Tenho plena consciência de que o que estou propondo não segue as diretrizes do .NET e, portanto, provavelmente é uma ideia ruim apenas por esse motivo. No entanto, gostaria de considerar isso de duas perspectivas possíveis:
(1) Devo considerar usar isso para meu próprio trabalho de desenvolvimento, que é 100% para fins internos.
(2) Este é um conceito que os projetistas da estrutura poderiam considerar alterar ou atualizar?
Estou pensando em usar uma assinatura de evento que utiliza um 'remetente' de tipo forte, em vez de digitá-lo como 'objeto', que é o padrão de design .NET atual. Ou seja, em vez de usar uma assinatura de evento padrão semelhante a esta:
class Publisher
{
public event EventHandler<PublisherEventArgs> SomeEvent;
}
Estou pensando em usar uma assinatura de evento que utiliza um parâmetro 'sender' de tipo forte, como segue:
Primeiro, defina um "StrongTypedEventHandler":
[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
Isso não é tão diferente de um Action <TSender, TEventArgs>, mas ao fazer uso de StrongTypedEventHandler
, garantimos que o TEventArgs deriva System.EventArgs
.
A seguir, como exemplo, podemos usar o StrongTypedEventHandler em uma classe de publicação da seguinte maneira:
class Publisher
{
public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;
protected void OnSomeEvent()
{
if (SomeEvent != null)
{
SomeEvent(this, new PublisherEventArgs(...));
}
}
}
O arranjo acima permitiria que os assinantes utilizassem um manipulador de eventos de tipo forte que não exigisse conversão:
class Subscriber
{
void SomeEventHandler(Publisher sender, PublisherEventArgs e)
{
if (sender.Name == "John Smith")
{
// ...
}
}
}
Sei perfeitamente que isso rompe com o padrão de manipulação de eventos .NET padrão; no entanto, tenha em mente que a contravariância permitiria ao assinante usar uma assinatura tradicional de tratamento de eventos, se desejado:
class Subscriber
{
void SomeEventHandler(object sender, PublisherEventArgs e)
{
if (((Publisher)sender).Name == "John Smith")
{
// ...
}
}
}
Ou seja, se um manipulador de eventos precisasse inscrever-se em eventos de tipos de objetos distintos (ou talvez desconhecidos), o manipulador poderia digitar o parâmetro 'remetente' como 'objeto' para lidar com toda a amplitude de objetos remetentes em potencial.
Além de quebrar as convenções (que é algo que não levo a sério, acredite em mim), não consigo pensar em nenhuma desvantagem nisso.
Pode haver alguns problemas de conformidade com CLS aqui. Isso é executado no Visual Basic .NET 2008 100% bem (eu testei), mas acredito que as versões anteriores do Visual Basic .NET até 2005 não têm covariância e contravariância delegadas. [Edit: eu testei isso desde então, e está confirmado: VB.NET 2005 e abaixo não podem lidar com isso, mas VB.NET 2008 é 100% bom. Consulte "Edição nº 2" abaixo.] Pode haver outras linguagens .NET que também apresentam problemas, não tenho certeza.
Mas não me vejo desenvolvendo para qualquer linguagem diferente de C # ou Visual Basic .NET, e não me importo em restringi-lo a C # e VB.NET para .NET Framework 3.0 e superior. (Eu não poderia imaginar voltar para 2.0 neste momento, para ser honesto.)
Alguém mais consegue pensar em um problema com isso? Ou isso simplesmente rompe tanto com as convenções que faz o estômago das pessoas revirar?
Aqui estão alguns links relacionados que encontrei:
(1) Diretrizes de Design de Eventos [MSDN 3.5]
(3) Padrão de assinatura de evento em .net [StackOverflow 2008]
Estou interessado na opinião de todos e de todos sobre isso ...
Desde já, obrigado,
Mike
Edição nº 1: Esta é uma resposta à postagem de Tommy Carlier :
Aqui está um exemplo funcional completo que mostra que os manipuladores de eventos de tipo forte e os manipuladores de eventos padrão atuais que usam um parâmetro de 'remetente de objeto' podem coexistir com essa abordagem. Você pode copiar e colar o código e executá-lo:
namespace csScrap.GenericEventHandling
{
class PublisherEventArgs : EventArgs
{
// ...
}
[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
class Publisher
{
public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;
public void OnSomeEvent()
{
if (SomeEvent != null)
{
SomeEvent(this, new PublisherEventArgs());
}
}
}
class StrongTypedSubscriber
{
public void SomeEventHandler(Publisher sender, PublisherEventArgs e)
{
MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called.");
}
}
class TraditionalSubscriber
{
public void SomeEventHandler(object sender, PublisherEventArgs e)
{
MessageBox.Show("TraditionalSubscriber.SomeEventHandler called.");
}
}
class Tester
{
public static void Main()
{
Publisher publisher = new Publisher();
StrongTypedSubscriber strongTypedSubscriber = new StrongTypedSubscriber();
TraditionalSubscriber traditionalSubscriber = new TraditionalSubscriber();
publisher.SomeEvent += strongTypedSubscriber.SomeEventHandler;
publisher.SomeEvent += traditionalSubscriber.SomeEventHandler;
publisher.OnSomeEvent();
}
}
}
Edição # 2: Esta é uma resposta à declaração de Andrew Hare sobre covariância e contravariância e como isso se aplica aqui. Os delegados na linguagem C # tiveram covariância e contravariância por tanto tempo que parece "intrínseca", mas não é. Pode até ser algo habilitado no CLR, não sei, mas o Visual Basic .NET não obteve capacidade de covariância e contravariância para seus delegados até o .NET Framework 3.0 (VB.NET 2008). E, como resultado, o Visual Basic.NET para .NET 2.0 e inferior não seria capaz de utilizar essa abordagem.
Por exemplo, o exemplo acima pode ser traduzido em VB.NET da seguinte forma:
Namespace GenericEventHandling
Class PublisherEventArgs
Inherits EventArgs
' ...
' ...
End Class
<SerializableAttribute()> _
Public Delegate Sub StrongTypedEventHandler(Of TSender, TEventArgs As EventArgs) _
(ByVal sender As TSender, ByVal e As TEventArgs)
Class Publisher
Public Event SomeEvent As StrongTypedEventHandler(Of Publisher, PublisherEventArgs)
Public Sub OnSomeEvent()
RaiseEvent SomeEvent(Me, New PublisherEventArgs)
End Sub
End Class
Class StrongTypedSubscriber
Public Sub SomeEventHandler(ByVal sender As Publisher, ByVal e As PublisherEventArgs)
MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called.")
End Sub
End Class
Class TraditionalSubscriber
Public Sub SomeEventHandler(ByVal sender As Object, ByVal e As PublisherEventArgs)
MessageBox.Show("TraditionalSubscriber.SomeEventHandler called.")
End Sub
End Class
Class Tester
Public Shared Sub Main()
Dim publisher As Publisher = New Publisher
Dim strongTypedSubscriber As StrongTypedSubscriber = New StrongTypedSubscriber
Dim traditionalSubscriber As TraditionalSubscriber = New TraditionalSubscriber
AddHandler publisher.SomeEvent, AddressOf strongTypedSubscriber.SomeEventHandler
AddHandler publisher.SomeEvent, AddressOf traditionalSubscriber.SomeEventHandler
publisher.OnSomeEvent()
End Sub
End Class
End Namespace
VB.NET 2008 pode executá-lo 100% bem. Mas agora eu testei no VB.NET 2005, apenas para ter certeza, e ele não compila, afirmando:
O método 'Public Sub SomeEventHandler (sender As Object, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' não tem a mesma assinatura que o delegado 'Delegate Sub StrongTypedEventHandler (Of TSender, TEventArgs As System.EventEventArgs) (egs '
Basicamente, os delegados são invariáveis nas versões do VB.NET 2005 e anteriores. Na verdade, tive essa ideia alguns anos atrás, mas a incapacidade do VB.NET de lidar com isso me incomodou ... Mas agora mudei solidamente para o C # e o VB.NET agora pode lidar com isso, então, bem, daí esta postagem.
Editar: Atualização # 3
Ok, eu tenho usado isso com bastante sucesso há algum tempo. É realmente um bom sistema. Decidi nomear meu "StrongTypedEventHandler" como "GenericEventHandler", definido da seguinte forma:
[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
Além dessa renomeação, implementei exatamente como discutido acima.
Ele tropeça na regra CA1009 do FxCop, que afirma:
"Por convenção, os eventos .NET têm dois parâmetros que especificam o remetente do evento e os dados do evento. As assinaturas do manipulador de eventos devem seguir este formato: void MyEventHandler (remetente do objeto, EventArgs e). O parâmetro 'remetente' é sempre do tipo System.Object, mesmo que seja possível empregar um tipo mais específico. O parâmetro 'e' é sempre do tipo System.EventArgs. Os eventos que não fornecem dados do evento devem usar o tipo de delegado System.EventHandler. Os manipuladores de eventos retornam void para que possam enviar cada evento para vários métodos de destino. Qualquer valor retornado por um destino seria perdido após a primeira chamada. "
Claro, sabemos tudo isso e, de qualquer maneira, estamos quebrando as regras. (Todos os manipuladores de eventos podem usar o 'remetente de objeto' padrão em sua assinatura, se preferir em qualquer caso - esta é uma alteração ininterrupta.)
Portanto, o uso de um SuppressMessageAttribute
resolve:
[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
Espero que essa abordagem se torne o padrão em algum momento no futuro. Realmente funciona muito bem.
Obrigado por todas as suas opiniões pessoal, eu realmente aprecio isso ...
Mike
oh hi this my hom work solve it plz :code dump:
perguntas do tamanho de um tweet , mas uma pergunta com a qual aprendemos .
EventHandler<,>
do que GenericEventHandler<,>
. Já existe um genérico EventHandler<>
no BCL que é denominado apenas EventHandler. Portanto, EventHandler é um nome mais comum e delegados suportam sobrecargas de tipo