the net fire event delegate and c# .net vb.net events

net - event sample c#



Firma de evento en.NET-¿Cómo usar un ''Remitente'' fuerte? (11)

Me doy cuenta completamente de que lo que propongo no sigue las pautas de .NET, y, por lo tanto, es probablemente una idea pobre por esta sola 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 fines internos?

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

Estoy pensando en utilizar una firma de evento que utilice un ''emisor'' tipeado fuerte, en lugar de escribirlo como ''objeto'', que es el patrón de diseño .NET actual. Es decir, en lugar de utilizar 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 ''emisor'' 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 muy diferente de Action <TSender, TEventArgs>, pero al usar StrongTypedEventHandler , hacemos cumplir que TEventArgs se deriva de System.EventArgs .

A continuación, como ejemplo, podemos hacer uso del 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") { // ... } } }

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 contravariancia permitiría al 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 necesitaba suscribirse a eventos de tipos de objetos dispares (o tal vez desconocidos), el manejador podía escribir el parámetro ''remitente'' como ''objeto'' para manejar toda la gama de posibles objetos remitentes.

Además de romper las convenciones (que es algo que no tomo a la ligera, créanme) no puedo pensar en ningún inconveniente para esto.

Puede haber algunos problemas de cumplimiento de CLS aquí. Esto se ejecuta en Visual Basic .NET 2008 100% fino (lo he probado), pero creo que las versiones anteriores de Visual Basic .NET hasta 2005 no tienen covarianza y contravarianza delegadas. [Editar: Desde entonces he probado esto, y está confirmado: VB.NET 2005 y versiones posteriores no pueden manejar esto, pero VB.NET 2008 es 100% correcto. Consulte "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 momento, para ser sincero.)

¿Alguien más puede pensar en un problema con esto? ¿O esto simplemente rompe con la convención tanto que hace que los estómagos de las personas cambien?

Aquí hay algunos enlaces relacionados que he encontrado:

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

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

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

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

Gracias por adelantado,

Micro

Editar # 1: Esto es en respuesta a la publicación de Tommy Carlier :

Aquí hay un ejemplo completo de trabajo que muestra que tanto los manejadores de eventos de tipo fuerte como los controladores de eventos estándar actuales que usan un parámetro ''remitente de objeto'' pueden coexistir con este enfoque. Puede copiar y pegar en 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(); } } }

Editar # 2: Esto es en respuesta a la declaració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 contravariancia 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 sé, pero Visual Basic .NET no obtuvo 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 probé en VB.NET 2005, solo para estar seguro, y no compila, indicando:

Método ''Public Sub SomeEventHandler (remitente como objeto, e como vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)'' no tiene la misma firma que delegar ''Delegar Sub StrongTypedEventHandler (de TSender, TEventArgs As System.EventArgs) (remitente como publicador, e como PublisherEventArgs) ''

Básicamente, los delegados son invariables en las versiones VB.NET 2005 y siguientes. 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, de ahí esta publicación.

Editar: Actualización n. ° 3

Ok, he estado usando esto con bastante éxito desde hace 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'' siempre es del tipo System.Object, incluso si es posible emplear un tipo más específico. El parámetro ''e'' siempre es del tipo System.EventArgs. Los eventos que no proporcionan datos de eventos deben usar el tipo de delegado System.EventHandler. Los manejadores de eventos devuelven el vacío para que puedan enviar cada evento a múltiples métodos objetivo. Cualquier valor devuelto por un objetivo se perderá después de la primera llamada ".

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

Entonces, el uso de un SuppressMessageAttribute hace 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, realmente lo aprecio ...

Micro


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

button.Click += ClickHandler; label.Click += ClickHandler; void ClickHandler(object sender, EventArgs e) { ... }

Si el remitente fuera genérico, el objetivo del evento click no sería de tipo Button o Label, 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.


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.


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

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

Este parece ser el mejor camino a seguir considerando el uso del nombre TypedEventHandler en WinRT.


Lo que estás proponiendo tiene mucho sentido en realidad, y me pregunto si esta es una de esas cosas simplemente así porque fue diseñada originalmente antes de los genéricos, o si hay una razón real para esto.


Mirando hacia atrás a la blasfemia como la única razón para hacer que el remitente sea un tipo de objeto (si omitir problemas con la 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 más allá, ¿hay alguna buena razón para cumplir con las pautas y convenciones de Microsoft en este caso particular?

Tener necesidad de desarrollar otro wrapper EventArgs para otra información que queremos pasar dentro del controlador de eventos parece extraño, por qué no se puede pasar directamente allí la información. 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; }


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

Si realmente desea hacer este cambio para una API pública, es posible que desee considerar la creación de su propia clase base EvenArgs. 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 como este

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

Y métodos como este:

private void HandleSomething(object sender, EventArgs e)

todavía podrá suscribirse.

EDITAR

Esa última línea me hizo pensar un poco ... En realidad, deberías poder implementar lo que propones sin romper ninguna funcionalidad externa ya que el tiempo de ejecución no tiene problemas para bajar los parámetros. Todavía me inclinaría por la solución DataEventArgs (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 del evento args.

Un beneficio de seguir con DataEventArgs es que puede encadenar eventos, cambiar el remitente (para representar al último remitente) mientras que EventArgs conserva el remitente original.


No estoy de acuerdo con las siguientes declaraciones:

  • Creo que las versiones anteriores de Visual Basic .NET hasta 2005 no tienen delegado covarianza y contravarianza.
  • Realmente me doy cuenta de que esto raya en la blasfemia.

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

En segundo lugar, estoy totalmente en desacuerdo con que su idea roza la "blasfemia" ya que es una idea maravillosa.


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

Delegados genéricos


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

Dado que es posible que un objeto pueda enviar eventos en nombre de otro objeto, puedo ver cierta utilidad potencial para tener un campo "remitente" que sea de tipo Objeto, y para que el campo derivado de EventArgs contenga una referencia al objeto que debería ser actuado sobre. Sin embargo, el uso del campo "remitente" probablemente esté limitado por el hecho de que no existe una forma clara para que un objeto se anule la suscripción de un remitente desconocido.

(*) En realidad, una forma más limpia de manejar las cancelaciones de suscripciones sería tener un tipo de delegado de multidifusión para las funciones que devuelven Boolean; 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 verdaderamente inmutables, pero debería ser posible efectuar dicho cambio de manera segura (por ejemplo, anulando la referencia del objeto y haciendo que el código de delegado de multidifusión ignore cualquier referencia de objeto nulo incorporado). Bajo este escenario, un intento de publicación y evento a un objeto eliminado podría manejarse muy limpiamente, sin importar de dónde provenga el evento.


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

public event Action<MyEventType> EventName

donde MyEventType no hereda de EventArgs . Por qué molestarse, si nunca intento usar ninguno de los miembros de EventArgs.


Windows Runtime (WinRT) introduce un TypedEventHandler<TSender, TResult> , que hace exactamente lo que hace su StrongTypedEventHandler<TSender, TResult> , pero aparentemente sin la restricción en el parámetro de tipo TResult :

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

La documentación de MSDN está here .