Firma del evento en .NET - ¿Usando un "remitente" con letra fuerte?

Comprendo completamente que lo que estoy proponiendo no sigue las directrices de .NET y, por lo tanto, es probablemente una mala idea solo por esta razón. Sin embargo, me gustaría considerar esto desde dos perspectivas posibles:

(1) Debo considerar usar esto para mi propio trabajo de desarrollo, que es 100% para propósitos internos.

(2) ¿Es este un concepto que los diseñadores del marco podrían considerar cambiar o actualizar?

Estoy pensando en usar una firma de evento que utilice un 'remitente' de tipo fuerte, en lugar de escribirlo como 'objeto', que es el patrón de diseño actual de .NET. Es decir, en lugar de usar una firma de evento estándar que se parece a esto:

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

Estoy considerando usar una firma de evento que utilice un parámetro de "remitente" de tipo fuerte, de la siguiente manera:

Primero, defina un "StrongTypedEventHandler":

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

Esto no es tan diferente de una Acción <TSender, TEventArgs>, sino haciendo uso de laStrongTypedEventHandler, hacemos cumplir que el TEventArgs deriva deSystem.EventArgs.

A continuación, como ejemplo, podemos hacer uso de StrongTypedEventHandler en una clase de publicación de la siguiente manera:

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

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

La disposición anterior permitiría a los suscriptores utilizar un controlador de eventos de tipo fuerte que no requería la conversión:

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

Realmente me doy cuenta de que esto rompe con el patrón estándar de manejo de eventos .NET; sin embargo, tenga en cuenta que la contravarianza permitiría a un suscriptor utilizar una firma de manejo de eventos tradicional si lo desea:

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

Es decir, si un controlador de eventos necesitaba suscribirse a eventos de tipos de objetos dispares (o quizás desconocidos), el controlador podría escribir el parámetro 'remitente' como 'objeto' para manejar la amplitud completa de posibles objetos remitentes.

Aparte de romper la convención (que es algo que no me tomo a la ligera, créeme) no puedo pensar en ninguna desventaja de esto.

Puede haber algunos problemas de cumplimiento de CLS aquí. Esto se ejecuta en Visual Basic .NET 2008 100% bien (lo he probado), pero creo que las versiones anteriores de Visual Basic .NET hasta 2005 no tienen covarianza y contravarianza delegada.[Edición: desde entonces he probado esto, y está confirmado: VB.NET 2005 y las versiones anteriores no pueden manejar esto, pero VB.NET 2008 está 100% bien. Vea "Editar # 2", a continuación.] Puede haber otros lenguajes .NET que también tengan un problema con esto, no estoy seguro.

Pero no me veo desarrollando para ningún otro lenguaje que no sea C # o Visual Basic .NET, y no me importa restringirlo a C # y VB.NET para .NET Framework 3.0 y superior. (No puedo imaginar volver a 2.0 en este punto, para ser honesto).

¿Alguien más puede pensar en un problema con esto? ¿O esto simplemente rompe con la convención tanto que hace que el estómago de la gente gire?

Aquí hay algunos enlaces relacionados que he encontrado:

(1)Pautas de diseño de eventos [MSDN 3.5]

(2)Evento simple de C #: uso de "remitente" frente a EventArgs personalizado [StackOverflow 2009]

(3)Patrón de firma de evento en .net [StackOverflow 2008]

Estoy interesado en la opinión de cualquiera y de todos sobre esto ...

Gracias por adelantado,

Micro

Edición # 1: Esto es en respuesta aMensaje de Tommy Carlier:

Este es un ejemplo completo de trabajo que muestra que tanto los controladores de eventos de tipo fuerte como los controladores de eventos estándar actuales que usan un parámetro de "remitente de objetos" pueden coexistir con este enfoque. Puedes copiar y pegar el código y ejecutarlo:

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

Edición # 2: Esto es en respuesta aDeclaración de Andrew Hare con respecto a la covarianza y la contravarianza y cómo se aplica aquí. Los delegados en el lenguaje C # han tenido covarianza y contravarianza durante tanto tiempo que simplemente se siente "intrínseco", pero no lo es. Incluso podría ser algo que esté habilitado en el CLR, no lo sé, pero Visual Basic .NET no obtuvo la capacidad de covarianza y contravarianza para sus delegados hasta el .NET Framework 3.0 (VB.NET 2008). Y como resultado, Visual Basic.NET para .NET 2.0 y versiones posteriores no podrían utilizar este enfoque.

Por ejemplo, el ejemplo anterior se puede traducir a VB.NET de la siguiente manera:

<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 puede ejecutarlo 100% bien. Pero ahora lo he probado en VB.NET 2005, solo para estar seguro, y no se compila, diciendo:

El método 'Public Sub SomeEventHandler (el remitente como objeto, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' no tiene la misma firma que el delegado 'Delegate Sub StrongTypedEventHandler (Of TSender, TEventArgs As System.EventArgs) (vendedor) '

Básicamente, los delegados son invariantes en las versiones de VB.NET 2005 y posteriores. Realmente pensé en esta idea hace un par de años, pero la incapacidad de VB.NET para lidiar con esto me molestó ... Pero ahora me he movido sólidamente a C #, y VB.NET ahora puede manejarlo, así que, bueno, por lo tanto esta publicación.

Edición: Actualización # 3

Ok, he estado usando esto bastante exitosamente por un tiempo ahora. Realmente es un buen sistema. Decidí llamar a mi "StrongTypedEventHandler" como "GenericEventHandler", definido de la siguiente manera:

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

Aparte de este cambio de nombre, lo implementé exactamente como se mencionó anteriormente.

Se dispara sobre la regla CA1009 de FxCop, que establece:

"Por convención, los eventos .NET tienen dos parámetros que especifican el remitente del evento y los datos del evento. Las firmas del controlador de eventos deben seguir este formulario: void MyEventHandler (objeto sender, EventArgs e). El parámetro 'sender' es siempre de tipo System.Object, incluso si es posible emplear un tipo más específico. El parámetro 'e' es siempre de tipo System.EventArgs. Los eventos que no proporcionan datos de eventos deben usar el tipo de delegado System.EventHandler. Los controladores de eventos devuelven el vacío para que puedan enviar cada evento a múltiples métodos de destino. Cualquier valor devuelto por un objetivo se perdería después de la primera llamada ".

Por supuesto, sabemos todo esto, y estamos rompiendo las reglas de todos modos. (Todos los controladores de eventos pueden usar el "remitente de objetos" estándar en su firma, si se prefiere en cualquier caso; este es un cambio que no rompe).

Así que el uso de unSuppressMessageAttribute Hace el truco:

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

Espero que este enfoque se convierta en el estándar en algún momento en el futuro. Realmente funciona muy bien.

Gracias por todas tus opiniones chicos, realmente lo aprecio ...

Micro

Respuestas a la pregunta(11)

Su respuesta a la pregunta