Assinatura de evento no .net - usando um forte 'remetente digitado'?

Eu compreendo perfeitamente que o que estou propondo não segue as diretrizes do .NET e, portanto, é provavelmente uma idéia 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) Esse é um conceito que os projetistas de frameworks poderiam considerar mudar ou atualizar?

Estou pensando em usar uma assinatura de evento que utiliza um 'remetente' forte e datilografado, em vez de digitá-lo como 'objeto', que é o padrão de design atual do .NET. Ou seja, em vez de usar uma assinatura de evento padrão que se parece com isso:

<code>class Publisher
{
    public event EventHandler<PublisherEventArgs> SomeEvent;
}
</code>

Eu estou pensando em usar uma assinatura de evento que utiliza um parâmetro 'sender' de tipo forte, da seguinte maneira:

Primeiro, defina um "StrongTypedEventHandler":

<code>[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
    TSender sender,
    TEventArgs e
)
where TEventArgs : EventArgs;
</code>

Isso não é tão diferente de uma ação <TSender, TEventArgs>, mas fazendo uso doStrongTypedEventHandler, nós reforçamos que o TEventArgs deriva deSystem.EventArgs.

Em seguida, como exemplo, podemos usar o StrongTypedEventHandler em uma classe de publicação da seguinte maneira:

<code>class Publisher
{
    public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;

    protected void OnSomeEvent()
    {
        if (SomeEvent != null)
        {
            SomeEvent(this, new PublisherEventArgs(...));
        }
    }
}
</code>

O arranjo acima permitiria que os assinantes utilizassem um manipulador de eventos de tipo forte que não requeria conversão:

<code>class Subscriber
{
    void SomeEventHandler(Publisher sender, PublisherEventArgs e)
    {           
        if (sender.Name == "John Smith")
        {
            // ...
        }
    }
}
</code>

Eu percebo que isso rompe com o padrão padrão de tratamento de eventos do .NET; no entanto, lembre-se de que a contravariância permitiria que um assinante usasse uma assinatura de manipulação de evento tradicional, se desejado:

<code>class Subscriber
{
    void SomeEventHandler(object sender, PublisherEventArgs e)
    {           
        if (((Publisher)sender).Name == "John Smith")
        {
            // ...
        }
    }
}
</code>

Ou seja, se um manipulador de eventos precisasse assinar eventos de tipos de objetos distintos (ou talvez desconhecidos), o manipulador poderia digitar o parâmetro 'remetente' como 'objeto' para lidar com a amplitude total dos potenciais objetos do remetente.

Além de quebrar convenção (que é algo que eu não tomo de ânimo leve, acredite em mim) não consigo pensar em quaisquer desvantagens para isso.

Pode haver alguns problemas de conformidade com o CLS aqui. Isso é executado no Visual Basic .NET 2008 100% bem (eu testei), mas acredito que as versões mais antigas do Visual Basic .NET até 2005 não têm delegação de covariância e contravariância.[Edit: Eu já testei isso, e está confirmado: VB.NET 2005 e abaixo não podem lidar com isso, mas o VB.NET 2008 é 100% bom. Veja "Editar # 2", abaixo.] Pode haver outras linguagens .NET que também tenham um problema com isso, não posso ter certeza.

Mas eu não me vejo desenvolvendo para qualquer idioma diferente de C # ou Visual Basic .NET, e não me importo de restringi-lo a C # e VB.NET para .NET Framework 3.0 e superior. (Eu não poderia imaginar voltar a 2.0 neste momento, para ser honesto.)

Alguém pode pensar em um problema com isso? Ou isso simplesmente quebra tanto as convenções que faz os estômagos das pessoas mudarem?

Aqui estão alguns links relacionados que encontrei:

(1)Diretrizes de design de eventos [MSDN 3.5]

(2)C # simple Event Raising - usando "remetente" vs. custom EventArgs [StackOverflow 2009]

(3)Padrão de assinatura de evento em .net [StackOverflow 2008]

Estou interessado na opinião de qualquer um e de todos sobre isso ...

Desde já, obrigado,

Mike

Editar 1: Isso é em resposta aPublicação de Tommy Carlier:

Aqui está um exemplo de trabalho completo que mostra que os manipuladores de eventos de tipos fortes e os manipuladores de eventos padrão atuais que usam um parâmetro 'remetente do objeto' podem coexistir com essa abordagem. Você pode copiar e colar no código e executá-lo:

<code>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();
        }
    }
}
</code>

Editar # 2: Isso é em resposta aDeclaração de Andrew Hare sobre covariância e contravariância e como ela se aplica aqui. Delegados na linguagem C # tiveram covariância e contravariância por tanto tempo que ela simplesmente parece "intrínseca", mas não é. Pode até ser algo que está 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 for .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 maneira:

<code>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
</code>

VB.NET 2008 pode executá-lo 100% bem. Mas agora eu testei no VB.NET 2005, só para ter certeza, e ele não compila, dizendo:

Método 'Public Sub SomeEventHandler (remetente como objeto, e como vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' não tem a mesma assinatura como representante 'Delegate Sub StrongTypedEventHandler (de TSender, TEventArgs como System.EventArgs) (remetente como Publisher, e como PublisherEventArgs) '

Basicamente, os delegados são invariantes nas versões 2005 e inferiores do VB.NET. Eu realmente pensei nessa idéia há alguns anos atrás, mas a incapacidade do VB.NET de lidar com isso me incomodou ... Mas agora eu mudei solidamente para o C #, e o VB.NET agora pode lidar com isso, então, bem, portanto esta postagem.

Editar: atualização 3

Ok, eu tenho usado isso com bastante sucesso por um tempo agora. É realmente um bom sistema. Decidi nomear meu "StrongTypedEventHandler" como "GenericEventHandler", definido da seguinte maneira:

<code>[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
    TSender sender,
    TEventArgs e
)
where TEventArgs : EventArgs;
</code>

Além desta renomeação, eu implementei exatamente como discutido acima.

Ele tropeça na regra FxCop CA1009, que afirma:

"Por convenção, os eventos .NET possuem dois parâmetros que especificam o remetente do evento e os dados do evento. As assinaturas do manipulador de eventos devem seguir este formulário: void MyEventHandler (objeto emissor, EventArgs e). O parâmetro 'sender' é 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 de 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 estamos violando as regras de qualquer maneira. (Todos os manipuladores de eventos podem usar o 'objeto Remetente' padrão em suas assinaturas, se preferirem, em qualquer caso - essa é uma alteração sem interrupção.)

Então o uso de umSuppressMessageAttribute faz o truque:

<code>[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
    Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
</code>

Espero que esta abordagem se torne o padrão em algum momento no futuro. Isso realmente funciona muito bem.

Obrigado por todas as suas opiniões pessoal, eu realmente aprecio isso ...

Mike

questionAnswers(11)

yourAnswerToTheQuestion