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 queGenericEventHandler<,>
. Já existe um genéricoEventHandler<>
no BCL que é denominado apenas EventHandler. Portanto, EventHandler é um nome mais comum e delegados suportam sobrecargas de tipoRespostas:
Parece que a Microsoft percebeu isso, já que um exemplo semelhante está agora no MSDN:
Delegados Genéricos
fonte
O que você está propondo faz muito sentido, na verdade, e eu só me pergunto se essa é uma daquelas coisas que é simplesmente assim porque foi originalmente projetada antes dos genéricos, ou se há um motivo real para isso.
fonte
O Windows Runtime (WinRT) apresenta um
TypedEventHandler<TSender, TResult>
delegado, que faz exatamente o que vocêStrongTypedEventHandler<TSender, TResult>
faz, mas aparentemente sem a restrição noTResult
parâmetro de tipo:A documentação do MSDN está aqui .
fonte
EventArgs
, é apenas uma convençãoargs
isso aconteceránull
se não houver dados de evento, portanto, parece que eles estão evitando usar um objeto essencialmente vazio por padrão. Eu estou supondo que a ideia original era que um método com um segundo parâmetro do tipoEventArgs
poderia lidar com qualquer evento porque os tipos sempre seriam compatíveis. Eles provavelmente estão percebendo agora que ser capaz de lidar com vários eventos diferentes com um método não é tão importante.Tenho problemas com as seguintes afirmações:
Em primeiro lugar, nada do que você fez aqui tem algo a ver com covariância ou contravariância.( Editar: a declaração anterior está errada; para obter mais informações, consulte Covariância e contravariância em delegados ). Esta solução funcionará bem em todas as versões do CLR 2.0 e superiores (obviamente isso não funcionará em um aplicativo CLR 1.0, pois usa genéricos).Em segundo lugar, discordo veementemente que a sua ideia beira a "blasfêmia", pois é uma ideia maravilhosa.
fonte
Dei uma olhada em como isso foi tratado com o novo WinRT e com base em outras opiniões aqui, e finalmente resolvi fazer assim:
Este parece ser o melhor caminho a seguir, considerando o uso do nome TypedEventHandler no WinRT.
fonte
Acho que é uma ótima ideia e a MS pode simplesmente não ter tempo ou interesse para investir em tornar isso melhor, como, por exemplo, quando mudou de ArrayList para listas baseadas em genéricas.
fonte
Pelo que entendi, o campo "Sender" sempre deve se referir ao objeto que contém a inscrição do evento. Se eu tivesse meus druthers, também haveria um campo contendo informações suficientes para cancelar a inscrição de um evento caso se tornasse necessário (*) (considere, por exemplo, um registrador de alterações que se inscreve em eventos de 'coleção alterada'; contém duas partes , um dos quais faz o trabalho real e contém os dados reais e o outro fornece um wrapper de interface pública, a parte principal pode conter uma referência fraca para a parte do wrapper. Se a parte do wrapper for coletada pelo lixo, isso significaria não havia mais ninguém interessado nos dados que estavam sendo coletados, e o logador de alterações deve, portanto, cancelar a inscrição de qualquer evento que receba).
Uma vez que é possível que um objeto possa enviar eventos em nome de outro objeto, posso ver alguma utilidade potencial em ter um campo "remetente" que é do tipo Object e em ter o campo derivado de EventArgs contendo uma referência ao objeto que deve ser posta em prática. A utilidade do campo "remetente", entretanto, é provavelmente limitada pelo fato de que não há uma maneira limpa de um objeto cancelar a assinatura de um remetente desconhecido.
(*) Na verdade, uma maneira mais limpa de lidar com os cancelamentos seria ter um tipo de delegado multicast para funções que retornam Boolean; se uma função chamada por tal delegado retornar True, o delegado será corrigido para remover esse objeto. Isso significaria que os delegados não seriam mais verdadeiramente imutáveis, mas deveria ser possível efetuar tal mudança de maneira thread-safe (por exemplo, anulando a referência do objeto e fazendo com que o código do delegado multicast ignore quaisquer referências de objetos nulos incorporados). Nesse cenário, uma tentativa de publicar um evento para um objeto descartado poderia ser tratada de forma muito limpa, não importando de onde o evento veio.
fonte
Olhando de volta para a blasfêmia como a única razão para fazer do remetente um tipo de objeto (se omitir problemas com contravariância no código VB 2005, que é um erro da Microsoft IMHO), alguém pode sugerir pelo menos um motivo teórico para acertar o segundo argumento do tipo EventArgs. Indo ainda mais longe, há um bom motivo para estar em conformidade com as diretrizes e convenções da Microsoft neste caso específico?
Ter a necessidade de desenvolver outro wrapper EventArgs para outros dados que queremos passar dentro do manipulador de eventos parece estranho, por que não podemos passar esses dados diretamente para lá. Considere as seguintes seções de código
[Exemplo 1]
[Exemplo 2]
fonte
Com a situação atual (remetente é objeto), você pode facilmente anexar um método a vários eventos:
Se o remetente fosse genérico, o destino do evento de clique não seria do tipo Botão ou Rótulo, mas do tipo Controle (porque o evento é definido em Controle). Portanto, alguns eventos na classe Button teriam um destino do tipo Control, outros teriam outros tipos de destino.
fonte
Não acho que haja nada de errado com o que você deseja fazer. Na maior parte, suspeito que o
object sender
parâmetro permanece para continuar a oferecer suporte ao código anterior ao 2.0.Se você realmente deseja fazer essa alteração para uma API pública, pode considerar a criação de sua própria classe EvenArgs base. Algo assim:
Então você pode declarar seus eventos como este
E métodos como este:
ainda será capaz de se inscrever.
EDITAR
Essa última linha me fez pensar um pouco ... Você deve realmente ser capaz de implementar o que propõe sem quebrar nenhuma funcionalidade externa, pois o tempo de execução não tem problemas para reduzir os parâmetros. Eu ainda me inclinaria para a
DataEventArgs
solução (pessoalmente). Eu faria isso, porém sabendo que é redundante, uma vez que o remetente está armazenado no primeiro parâmetro e como uma propriedade dos argumentos do evento.Um benefício de manter o
DataEventArgs
é que você pode encadear eventos, alterando o remetente (para representar o último remetente) enquanto EventArgs retém o remetente original.fonte
Vá em frente. Para código não baseado em componente, muitas vezes simplifico as assinaturas de eventos para serem simplesmente
de onde
MyEventType
não herdaEventArgs
. Por que se preocupar, se nunca pretendo usar nenhum dos membros do EventArgs.fonte
event Action<S, T>
,event Action<R, S, T>
etc. Eu tenho um método de extensão paraRaise
eles thread-