Assinatura de evento em .NET - usando um 'remetente' de tipo forte?

106

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]

(2) Levantamento de evento simples C # - usando “remetente” versus EventArgs personalizados [StackOverflow 2009]

(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 SuppressMessageAttributeresolve:

[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

Mike Rosenblum
fonte
6
Faça. (Não pense que isso justifica uma resposta.)
Konrad Rudolph
1
Meus argumentos não foram realmente apontados para você: é claro que você deve fazer isso em seus próprios projetos. Eram argumentos para explicar por que isso poderia não funcionar no BCL.
Tommy Carlier
3
Cara, eu queria que meu projeto tivesse feito isso desde o início, odeio escalar o remetente.
Matt H
7
Agora ESTA é uma pergunta. Veja, pessoal? Não uma dessas oh hi this my hom work solve it plz :code dump:perguntas do tamanho de um tweet , mas uma pergunta com a qual aprendemos .
Camilo Martin de
3
Outra sugestão, apenas nome 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
nawfal

Respostas:

25

Parece que a Microsoft percebeu isso, já que um exemplo semelhante está agora no MSDN:

Delegados Genéricos

Bas
fonte
2
+1 Ah, excelente. Eles perceberam isso de fato. Isso é bom. Espero, porém, que eles tornem esse padrão reconhecido dentro do VS IDE, porque, como é agora, é mais difícil usar esse padrão em termos de IntelliSense, etc.
Mike Rosenblum
13

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.

BFree
fonte
1
Tenho certeza de que esse é exatamente o motivo. No entanto, agora que as versões mais recentes da linguagem têm contravariância para lidar com isso, parece que eles devem ser capazes de lidar com isso de uma maneira compatível com versões anteriores. Os manipuladores anteriores que usam um 'objeto remetente' não seriam interrompidos. Mas isso não é verdade para linguagens mais antigas e pode não ser verdade para algumas linguagens .NET atuais, não tenho certeza.
Mike Rosenblum
13

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 no TResultparâmetro de tipo:

public delegate void TypedEventHandler<TSender, TResult>(TSender sender,
                                                         TResult args);

A documentação do MSDN está aqui .

Pierre Arnaud
fonte
1
Ah, bom ver que há progresso ... Eu me pergunto por que TResult não está restrito a herdar da classe 'EventArgs'. A classe base 'EventArgs' está basicamente vazia; talvez eles estejam se afastando dessa restrição?
Mike Rosenblum
Pode ser um descuido da equipe de design; quem sabe.
Pierre Arnaud
bem, os eventos funcionam bem sem usar EventArgs, é apenas uma convenção
Sebastian,
3
Ele afirma especificamente na documentação do TypedEventHandler que argsisso acontecerá nullse 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 tipo EventArgspoderia 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.
jmcilhinney
1
Não parece um descuido. A restrição foi removida do delegado System.EventHandler <TEventArgs> também. referênciasource.microsoft.com
mscorlib
5

Tenho problemas com as seguintes afirmações:

  • Eu acredito que as versões mais antigas do Visual Basic .NET até 2005 não têm covariância e contravariância delegadas.
  • Sei perfeitamente que isso beira a blasfêmia.

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.

Andrew Hare
fonte
2
Olá, Andrew, obrigado pelo polegar para cima! Considerando o seu nível de reputação, isso realmente significa muito para mim ... Sobre a questão da covariância / contravariância: se o delegado fornecido pelo assinante não corresponder exatamente à assinatura do evento do editor, então a covariância e a contravariância estão envolvidas. C # teve covariância e contravariância delegadas desde sempre, então parece intrínseco, mas o VB.NET não tinha covariância e contravariância delegadas até .NET 3.0. Portanto, o VB.NET para .NET 2.0 e inferior não seria capaz de usar este sistema. (Veja o exemplo de código que adicionei em "Edit # 2", acima.)
Mike Rosenblum
@Mike - Minhas desculpas, você está 100% correto! Eu editei minha resposta para refletir seu ponto :)
Andrew Hare
4
Ah, interessante! Parece que a covariância / contravariância delegada faz parte do CLR, mas (por razões que não sei) não foi exposta pelo VB.NET antes da versão mais recente. Aqui está um artigo de Francesco Balena que mostra como a variação de delegado pode ser alcançada usando o Reflection, se não habilitado pelo próprio idioma: dotnet2themax.com/blogs/fbalena/… .
Mike Rosenblum
1
@Mike - É sempre interessante aprender as coisas que o CLR suporta, mas que não são suportadas em nenhuma das linguagens .NET.
Andrew Hare
5

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:

[Serializable]
public delegate void TypedEventHandler<in TSender, in TEventArgs>(
    TSender sender,
    TEventArgs e
) where TEventArgs : EventArgs;

Este parece ser o melhor caminho a seguir, considerando o uso do nome TypedEventHandler no WinRT.

Inverness
fonte
Por que adicionar a restrição genérica em TEventArgs? Ele foi removido de EventHandler <> e TypedEventHandler <,> porque realmente não fazia sentido.
Mike Marynowski
2

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.

Otávio Décio
fonte
Você pode estar certo ... Por outro lado, acho que é apenas um "padrão", e possivelmente nem um problema técnico. Ou seja, esse recurso pode estar presente em todas as linguagens .NET atuais, não sei. Eu sei que o C # e o VB.NET podem lidar com isso. No entanto, não tenho certeza de como isso funciona amplamente em todas as linguagens .NET atuais ... Mas, como funciona em C # e VB.NET, e todos aqui dão muito apoio, acho que provavelmente vou fazer isso. :-)
Mike Rosenblum
2

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.

supergato
fonte
2

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]

public delegate void ConnectionEventHandler(Server sender, Connection connection);

public partial class Server
{
    protected virtual void OnClientConnected(Connection connection)
    {
        if (ClientConnected != null) ClientConnected(this, connection);
    }

    public event ConnectionEventHandler ClientConnected;
}

[Exemplo 2]

public delegate void ConnectionEventHandler(object sender, ConnectionEventArgs e);

public class ConnectionEventArgs : EventArgs
{
    public Connection Connection { get; private set; }

    public ConnectionEventArgs(Connection connection)
    {
        this.Connection = connection;
    }
}

public partial class Server
{
    protected virtual void OnClientConnected(Connection connection)
    {
        if (ClientConnected != null) ClientConnected(this, new ConnectionEventArgs(connection));
    }

    public event ConnectionEventHandler ClientConnected;
}
Lu4
fonte
2
Sim, criar uma classe separada que herda de System.EventArgs pode parecer pouco intuitivo e representa um trabalho extra, mas há um bom motivo para isso. Se você nunca precisa alterar seu código, sua abordagem é adequada. Mas a realidade é que você pode precisar aumentar a funcionalidade do evento em uma versão futura e adicionar propriedades aos argumentos do evento. Em seu cenário, você teria que adicionar sobrecargas extras ou parâmetros opcionais à assinatura do manipulador de eventos. Esta é uma abordagem usada no VBA e no VB 6.0 legado, que é viável, mas um pouco feia na prática.
Mike Rosenblum
1
Ao herdar de EventArgs, no entanto, uma versão futura poderia herdar de sua classe de argumentos de evento mais antiga e aumentá-la. Todos os chamadores mais antigos ainda podem funcionar exatamente como estão, operando contra a classe base de sua nova classe de argumentos de evento. Muito limpo. Mais trabalho para você, mas mais limpo para qualquer chamador que dependa de sua biblioteca.
Mike Rosenblum
Ele nem mesmo precisa herdá-lo, você pode apenas adicionar a funcionalidade adicional diretamente em sua classe de args de evento e ele continuará a funcionar bem. Dito isso, a restrição que prendia args a eventargs foi removida porque não fazia muito sentido para muitos cenários, ou seja, quando você sabe que nunca precisará expandir a funcionalidade de um evento específico ou quando tudo o que você precisa é um tipo de valor arg em aplicativos muito sensíveis ao desempenho.
Mike Marynowski
1

Com a situação atual (remetente é objeto), você pode facilmente anexar um método a vários eventos:

button.Click += ClickHandler;
label.Click += ClickHandler;

void ClickHandler(object sender, EventArgs e) { ... }

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.

Tommy Carlier
fonte
2
Tommy, você pode fazer exatamente a mesma coisa com o sistema que estou propondo. Você ainda pode usar um manipulador de eventos padrão que tenha um parâmetro de 'remetente de objeto' para manipular esses eventos de tipo forte. (Veja o exemplo de código que agora adicionei à postagem original.)
Mike Rosenblum
Sim, concordo, essa é a vantagem dos eventos .NET padrão, aceitos!
Lu4
1

Não acho que haja nada de errado com o que você deseja fazer. Na maior parte, suspeito que o object senderparâ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:

public class DataEventArgs<TSender, TData> : EventArgs
{
    private readonly TSender sender, TData data;

    public DataEventArgs(TSender sender, TData data)
    {
        this.sender = sender;
        this.data = data;
    }

    public TSender Sender { get { return sender; } }
    public TData Data { get { return data; } }
}

Então você pode declarar seus eventos como este

public event EventHandler<DataEventArgs<MyClass, int>> SomeIndexSelected;

E métodos como este:

private void HandleSomething(object sender, EventArgs e)

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 DataEventArgssoluçã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.

Michael Meadows
fonte
Ei Michael, esta é uma alternativa muito legal. Eu gosto disso. Como você mencionou, porém, é redundante ter o parâmetro 'sender' efetivamente passado duas vezes. Uma abordagem semelhante é discutida aqui: stackoverflow.com/questions/809609/… , e o consenso parece ser que é muito fora do padrão. É por isso que hesitei em sugerir aqui a ideia de 'remetente' com tipo forte. (Parece ter sido bem recebido, então estou satisfeito.)
Mike Rosenblum
1

Vá em frente. Para código não baseado em componente, muitas vezes simplifico as assinaturas de eventos para serem simplesmente

public event Action<MyEventType> EventName

de onde MyEventTypenão herda EventArgs. Por que se preocupar, se nunca pretendo usar nenhum dos membros do EventArgs.

Scott Weinstein
fonte
1
Aceita! Por que deveríamos nos sentir macacos?
Lu4
1
+ 1-ed, isto é o que eu também uso. Às vezes, a simplicidade vence! Ou mesmo event Action<S, T>, event Action<R, S, T>etc. Eu tenho um método de extensão para Raiseeles thread-
safe