Firma de evento en .NET: ¿utiliza un 'remitente' de tipo seguro?

106

Me doy cuenta de que lo que propongo no sigue las pautas de .NET y, por lo tanto, probablemente sea una mala idea solo por esta razón. Sin embargo, me gustaría considerar esto desde dos perspectivas posibles:

(1) ¿Debería 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 de .NET actual. Es decir, en lugar de usar una firma de evento estándar que se ve así:

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

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

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

Esto no es tan diferente de un Action <TSender, TEventArgs>, pero al hacer uso del StrongTypedEventHandler, hacemos cumplir que el TEventArgs se deriva de System.EventArgs.

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

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

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

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

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

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

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

Es decir, si un controlador de eventos necesita suscribirse a eventos de tipos de objetos dispares (o tal vez desconocidos), el controlador podría escribir el parámetro 'remitente' como 'objeto' para manejar la gama completa de objetos remitentes potenciales.

Aparte de romper las convenciones (que es algo que no tomo a la ligera, créanme), 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 delegadas. [Editar: he probado esto desde entonces, y está confirmado: VB.NET 2005 y versiones anteriores no pueden manejar esto, pero VB.NET 2008 está 100% bien. Consulte "Edición nº 2", a continuación.] Puede que haya otros lenguajes .NET que también tengan un problema con esto, no puedo estar seguro.

Pero no me veo desarrollando para ningún 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. (Para ser honesto, no me puedo imaginar volver a 2.0 en este punto).

¿Alguien más puede pensar en un problema con esto? ¿O esto simplemente rompe con las convenciones tanto que hace que a la gente se le revuelva el estómago?

Aquí hay algunos enlaces relacionados que encontré:

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

(2) Generación de eventos simple en C #: uso de "remitente" frente a EventArgs personalizados [StackOverflow 2009]

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

Me interesa la opinión de todos y de todos sobre esto ...

Gracias por adelantado,

Miguel

Edición n. ° 1: esto es en respuesta a la publicación de Tommy Carlier :

Aquí hay un ejemplo de trabajo completo 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 objeto' pueden coexistir con este enfoque. Puede copiar y pegar el código y ejecutarlo:

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

Edición # 2: Esto es en respuesta a la declaración de Andrew Hare con respecto a la covarianza y contravarianza y cómo se aplica aquí. Los delegados en el lenguaje C # han tenido covarianza y contravarianza durante tanto tiempo que se siente "intrínseco", pero no lo es. Incluso podría ser algo que esté habilitado en CLR, no lo sé, pero Visual Basic .NET no obtuvo la capacidad de covarianza y contravarianza para sus delegados hasta .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:

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

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

Básicamente, los delegados son invariantes en las versiones de VB.NET 2005 y anteriores. De hecho, 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.

Editar: Actualización n. ° 3

Ok, he estado usando esto con bastante éxito por un tiempo. Realmente es un buen sistema. Decidí nombrar mi "StrongTypedEventHandler" como "GenericEventHandler", definido de la siguiente manera:

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

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

Se tropieza con 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 (remitente del objeto, EventArgs e). El parámetro 'remitente' 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 void para que puedan enviar cada evento a varios métodos de destino. Cualquier valor devuelto por un destino se perdería después de la primera llamada ".

Por supuesto, sabemos todo esto y, de todos modos, estamos rompiendo las reglas. (Todos los controladores de eventos pueden usar el 'remitente de objeto' estándar en su firma si lo prefieren en cualquier caso; este es un cambio irrefutable).

Entonces, el uso de a SuppressMessageAttributehace el truco:

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

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

Gracias por todas sus opiniones chicos, de verdad se lo agradezco ...

Miguel

Mike Rosenblum
fuente
6
Hazlo. (No crea que esto justifica una respuesta)
Konrad Rudolph
1
Mis argumentos no fueron realmente apuntados a usted: por supuesto, debería hacer esto en sus propios proyectos. Eran argumentos por los que podría no funcionar en la BCL.
Tommy Carlier
3
Hombre, desearía que mi proyecto hubiera hecho esto desde el principio, odio elegir al remitente.
Matt H
7
Ahora ESTO es una pregunta. ¿Ven, amigos? No es una de estas oh hi this my hom work solve it plz :code dump:preguntas del tamaño de un tweet , sino una pregunta de la que aprendemos .
Camilo Martin
3
Otra sugerencia, solo nómbrala EventHandler<,>que GenericEventHandler<,>. Ya hay genérico EventHandler<>en BCL que se llama simplemente EventHandler. Entonces, EventHandler es un nombre más común y los delegados admiten sobrecargas de tipos
nawfal

Respuestas:

25

Parece que Microsoft se ha dado cuenta de esto, ya que ahora hay un ejemplo similar en MSDN:

Delegados genéricos

Bas
fuente
2
+1 Ah, excelente. De hecho, se han dado cuenta de esto. Esto es bueno. Sin embargo, espero que hagan de este un patrón reconocido dentro del IDE de VS, porque, como está ahora, es más incómodo usar este patrón en términos de IntelliSense, etc.
Mike Rosenblum
13

Lo que propones tiene mucho sentido en realidad, y me pregunto si esta es una de esas cosas que es simplemente la forma en que es porque fue diseñado originalmente antes de los genéricos, o si hay una razón real para esto.

BFree
fuente
1
Estoy seguro de que esta es exactamente la razón. Sin embargo, ahora que las versiones más nuevas del lenguaje tienen contravarianza para manejar esto, parece que deberían poder manejar esto de una manera compatible con versiones anteriores. Los controladores anteriores que usaban un 'objeto remitente' no se interrumpían. Pero esto no es cierto para los lenguajes más antiguos y puede que no sea cierto para algunos lenguajes .NET actuales, no estoy seguro.
Mike Rosenblum
13

Windows Runtime (WinRT) presenta un TypedEventHandler<TSender, TResult>delegado, que hace exactamente lo que hace usted StrongTypedEventHandler<TSender, TResult>, pero aparentemente sin la restricción del TResultparámetro de tipo:

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

La documentación de MSDN está aquí .

Pierre Arnaud
fuente
1
Ah, es bueno ver que hay progreso ... Me pregunto por qué TResult no está restringido a heredar de la clase 'EventArgs'. La clase base 'EventArgs' está básicamente vacía; ¿tal vez se están alejando de esta restricción?
Mike Rosenblum
Podría ser un descuido del equipo de diseño; quién sabe.
Pierre Arnaud
Bueno, los eventos funcionan bien sin usar EventArgs, es solo una convención
Sebastian
3
Indica específicamente en la documentación de TypedEventHandler que argsserá nullsi no hay datos de eventos, por lo que parece que se están alejando del uso de un objeto esencialmente vacío de forma predeterminada. Supongo que la idea original era que un método con un segundo parámetro de tipo EventArgspodría manejar cualquier evento porque los tipos siempre serían compatibles. Probablemente se estén dando cuenta ahora de que poder manejar múltiples eventos diferentes con un método no es tan importante.
jmcilhinney
1
No parece un descuido. La restricción también se eliminó del delegado System.EventHandler <TEventArgs>. referenciasource.microsoft.com/#mscorlib/system/…
colton7909
5

No estoy de acuerdo con las siguientes declaraciones:

  • Creo que las versiones anteriores de Visual Basic .NET hasta 2005 no tienen covarianza ni contravarianza delegadas.
  • Soy plenamente consciente de que esto raya en la blasfemia.

En primer lugar, nada de lo que haya hecho aquí tiene nada que ver con la covarianza o contravarianza. ( Editar: la declaración anterior es incorrecta, para obtener más información, consulte Covarianza y contravarianza en delegados ) Esta solución funcionará bien en todas las versiones de CLR 2.0 y posteriores (obviamente, esto no funcionará en una aplicación CLR 1.0 ya que usa genéricos).

En segundo lugar, no estoy de acuerdo con que su idea raye en la "blasfemia", ya que es una idea maravillosa.

Andrew Hare
fuente
2
Hola Andrew, ¡gracias por el visto bueno! Teniendo en cuenta su nivel de reputación, esto realmente significa mucho para mí ... En el tema de la covarianza / contravarianza: si el delegado proporcionado por el suscriptor no coincide exactamente con la firma del evento del editor, entonces la covarianza y la contravarianza están involucradas. C # ha tenido covarianza y contravarianza delegadas desde siempre, por lo que se siente intrínseco, pero VB.NET no tenía covarianza y contravarianza delegadas hasta .NET 3.0. Por lo tanto, VB.NET para .NET 2.0 e inferior no podría utilizar este sistema. (Vea el ejemplo de código que agregué en la "Edición n. ° 2", arriba).
Mike Rosenblum
@Mike - ¡Mis disculpas, estás 100% correcto! He editado mi respuesta para reflejar su punto :)
Andrew Hare
4
¡Ah, interesante! Parece que la covarianza / contravarianza delegada es parte de CLR, pero (por razones que no sé) no fue expuesta por VB.NET antes de la versión más reciente. Aquí hay un artículo de Francesco Balena que muestra cómo se puede lograr la variación de delegados usando Reflection, si no está habilitado por el idioma en sí: dotnet2themax.com/blogs/fbalena/… .
Mike Rosenblum
1
@Mike: siempre es interesante aprender las cosas que admite CLR pero que no son compatibles con ninguno de los lenguajes .NET.
Andrew Hare
5

Eché un vistazo a cómo se manejó esto con el nuevo WinRT y me basé en otras opiniones aquí, y finalmente me decidí a hacerlo así:

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

Esta parece ser la mejor manera de avanzar considerando el uso del nombre TypedEventHandler en WinRT.

Inverness
fuente
¿Por qué agregar la restricción genérica en TEventArgs? Se eliminó de EventHandler <> y TypedEventHandler <,> porque realmente no tenía sentido.
Mike Marynowski
2

Creo que es una gran idea y es posible que MS simplemente no tenga el tiempo o el interés para invertir en mejorar esto, como por ejemplo cuando pasaron de ArrayList a listas genéricas.

Otávio Décio
fuente
Puede que tengas razón ... Por otro lado, creo que es solo un "estándar" y posiblemente no sea un problema técnico en absoluto. Es decir, esta capacidad podría estar presente en todos los lenguajes .NET actuales, no lo sé. Sé que C # y VB.NET pueden manejar esto. Sin embargo, no estoy seguro de cuán ampliamente funciona esto en todos los lenguajes .NET actuales ... Pero como funciona en C # y VB.NET, y todos aquí me apoyan mucho, creo que es muy probable que haga esto. :-)
Mike Rosenblum
2

Por lo que tengo entendido, siempre se supone que el campo "Remitente" se refiere al objeto que contiene la suscripción al evento. Si tuviera mis druthers, también habría un campo con información suficiente para cancelar la suscripción de un evento si fuera necesario (*) (considere, por ejemplo, un registrador de cambios que se suscribe a eventos de 'colección cambiada'; contiene dos partes , uno de los cuales hace el trabajo real y contiene los datos reales, y el otro proporciona un contenedor de interfaz público, la parte principal podría contener una referencia débil a la parte del contenedor. Si la parte del contenedor se recolecta como basura, eso significaría ya no había nadie interesado en los datos que se estaban recopilando, por lo que el registrador de cambios debería darse de baja de cualquier evento que reciba).

Dado que es posible que un objeto envíe eventos en nombre de otro objeto, puedo ver alguna utilidad potencial para tener un campo "remitente" que es de tipo Objeto y para que el campo derivado de EventArgs contenga una referencia al objeto que debería ser actuado. Sin embargo, lo inútil del campo "remitente" probablemente esté limitado por el hecho de que no existe una forma clara de que un objeto se dé de baja de un remitente desconocido.

(*) En realidad, una forma más limpia de manejar las cancelaciones de suscripción sería tener un tipo de delegado de multidifusión para las funciones que devuelven booleanos; si una función llamada por dicho delegado devuelve True, el delegado será parcheado para eliminar ese objeto. Esto significaría que los delegados ya no serían realmente inmutables, pero debería ser posible efectuar dicho cambio de manera segura para subprocesos (por ejemplo, anulando la referencia del objeto y haciendo que el código del delegado de multidifusión ignore cualquier referencia de objeto nulo incrustado). En este escenario, un intento de publicar un evento en un objeto eliminado podría manejarse de manera muy limpia, sin importar de dónde provenga el evento.

Super gato
fuente
2

Mirando hacia atrás a la blasfemia como la única razón para hacer que el remitente sea un tipo de objeto (si se omiten los problemas de contravarianza en el código VB 2005, que es un error de Microsoft en mi humilde opinión), ¿alguien puede sugerir al menos un motivo teórico para clavar el segundo argumento al tipo EventArgs. Yendo aún más lejos, ¿hay una buena razón para cumplir con las pautas y convenciones de Microsoft en este caso particular?

Tener la necesidad de desarrollar otro contenedor EventArgs para otros datos que queremos pasar dentro del controlador de eventos parece extraño, ¿por qué no podemos pasar directamente esos datos allí? Considere las siguientes secciones de código

[Ejemplo 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;
}

[Ejemplo 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
fuente
2
Sí, crear una clase separada que herede de System.EventArgs puede parecer poco intuitivo y representa un trabajo adicional, pero hay una muy buena razón para ello. Si nunca necesita cambiar su código, entonces su enfoque está bien. Pero la realidad es que es posible que deba aumentar la funcionalidad del evento en una versión futura y agregar propiedades a los argumentos del evento. En su escenario, tendría que agregar sobrecargas adicionales o parámetros opcionales a la firma del controlador de eventos. Este es un enfoque utilizado en VBA y VB 6.0 heredado, que es viable, pero un poco desagradable en la práctica.
Mike Rosenblum
1
Sin embargo, al heredar de EventArgs, una versión futura podría heredar de su clase de argumentos de evento anterior y aumentarla. Todos los llamadores más antiguos podrían seguir funcionando exactamente como están, operando contra la clase base de su nueva clase de argumentos de eventos. Muy limpio. Más trabajo para usted, pero más limpio para cualquier persona que llame que dependa de su biblioteca.
Mike Rosenblum
Ni siquiera necesita heredarlo, simplemente puede agregar la funcionalidad adicional directamente en su clase de argumentos de evento y seguirá funcionando bien. Dicho esto, la restricción que fijaba argumentos a eventos se eliminó porque no tenía mucho sentido para muchos escenarios, es decir. cuando sabe que nunca necesitará expandir la funcionalidad de un evento en particular o cuando todo lo que necesita es un tipo de valor arg en aplicaciones muy sensibles al rendimiento.
Mike Marynowski
1

Con la situación actual (el remitente es un objeto), puede adjuntar fácilmente un método a varios eventos:

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

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

Si el remitente fuera genérico, el destino del evento de clic no sería de tipo Botón o Etiqueta, sino de tipo Control (porque el evento está definido en Control). Entonces, algunos eventos en la clase Button tendrían un objetivo de tipo Control, otros tendrían otros tipos de objetivos.

Tommy Carlier
fuente
2
Tommy, puedes hacer exactamente lo mismo con el sistema que te propongo. Aún puede usar un controlador de eventos estándar que tenga un parámetro de 'remitente de objeto' para manejar estos eventos de tipo fuerte. (Vea el ejemplo de código que ahora agregué a la publicación original.)
Mike Rosenblum
Sí, estoy de acuerdo, esto es lo bueno de los eventos .NET estándar, ¡aceptado!
Lu4
1

No creo que haya nada de malo en lo que quieres hacer. En su mayor parte, sospecho que el object senderparámetro permanece para continuar admitiendo el código anterior a 2.0.

Si realmente desea realizar este cambio para una API pública, es posible que desee considerar la posibilidad de crear su propia clase EvenArgs base. Algo como esto:

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

Entonces puedes declarar tus eventos así

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

Y métodos como este:

private void HandleSomething(object sender, EventArgs e)

aún podrá suscribirse.

EDITAR

Esa última línea me hizo pensar un poco ... Debería poder implementar lo que propone sin romper ninguna funcionalidad externa, ya que el tiempo de ejecución no tiene problemas para reducir los parámetros. Todavía me inclinaría hacia la DataEventArgssolución (personalmente). Lo haría, sin embargo, sabiendo que es redundante, ya que el remitente se almacena en el primer parámetro y como una propiedad de los argumentos del evento.

Un beneficio de seguir con el DataEventArgses que puede encadenar eventos, cambiando el remitente (para representar al último remitente) mientras que EventArgs retiene el remitente original.

Michael Meadows
fuente
Hola Michael, esta es una buena alternativa. Me gusta. Sin embargo, como mencionas, es redundante que el parámetro 'remitente' se pase efectivamente dos veces. Aquí se analiza un enfoque similar: stackoverflow.com/questions/809609/… , y el consenso parece ser que no es estándar. Es por eso que dudé en sugerir aquí la idea de 'remitente' de tipo fuerte. (Sin embargo, parece ser bien recibido, así que estoy contento.)
Mike Rosenblum
1

Ve a por ello. Para el código no basado en componentes, a menudo simplifico las firmas de eventos para que sean simples

public event Action<MyEventType> EventName

de donde MyEventTypeno hereda EventArgs. ¿Por qué molestarme, si nunca tengo la intención de utilizar ninguno de los miembros de EventArgs?

Scott Weinstein
fuente
1
¡De acuerdo! ¿Por qué deberíamos sentirnos monos?
Lu4
1
+ 1-ed, esto es lo que yo también uso. ¡A veces gana la simplicidad! O incluso event Action<S, T>, event Action<R, S, T>etc. Tengo un método de extensión para Raiseellos de forma segura :)
nawfal