c# - tipos - evento Acción<> vs evento EventHandler<>
eventhandler c# tutorial (6)
Basado en algunas de las respuestas anteriores, voy a dividir mi respuesta en tres áreas.
Primero, las limitaciones físicas de usar Action<T1, T2, T2... >
frente a usar una clase derivada de EventArgs
. Hay tres: Primero, si cambia el número o los tipos de parámetros, cada método que se suscriba tendrá que cambiarse para ajustarse al nuevo patrón. Si este es un evento público que las asambleas de terceros utilizarán, y existe la posibilidad de que los eventos args cambien, esta sería una razón para usar una clase personalizada derivada de eventos args para fines de coherencia (recuerde, usted PODRÍA aún use una Action<MyCustomClass>
) En segundo lugar, utilizando Action<T1, T2, T2... >
evitará que envíe comentarios de regreso al método de llamada a menos que tenga algún tipo de objeto (con una propiedad de Handled, por ejemplo) que sea pasado junto con la Acción. En tercer lugar, no obtienes parámetros con nombre, por lo que si pasas 3 bool
''s a int
, dos string
y DateTime
, no tienes idea del significado de esos valores. Como nota al margen, aún puede tener un método "Incendiar este evento de forma segura mientras sigue usando Action<T1, T2, T2... >
".
En segundo lugar, implicaciones de consistencia. Si tiene un sistema grande con el que ya está trabajando, casi siempre es mejor seguir la misma forma que el resto del sistema, a menos que tenga una razón muy buena. Si tiene eventos públicos que deben mantenerse, la capacidad de sustituir las clases derivadas puede ser importante. Mantenlo en mente.
En tercer lugar, la práctica de la vida real, personalmente encuentro que tiendo a crear muchos eventos únicos para cosas como cambios de propiedad con los que necesito interactuar (particularmente cuando hago MVVM con modelos de vista que interactúan entre sí) o donde el evento tiene un solo parámetro La mayoría de las veces estos eventos toman la forma de public event Action<[classtype], bool> [PropertyName]Changed;
o public event Action SomethingHappened;
. En estos casos, hay dos beneficios. Primero, obtengo un tipo para la clase emisora. Si MyClass
declara y es la única clase que activa el evento, obtengo una instancia explícita de MyClass
para trabajar en el controlador de eventos. En segundo lugar, para eventos simples como eventos de cambio de propiedad, el significado de los parámetros es obvio y se expresa en el nombre del controlador de eventos y no tengo que crear una gran cantidad de clases para este tipo de eventos.
¿Hay alguna diferencia entre declarar event Action<>
y event EventHandler<>
.
Suponiendo que no importa qué objeto realmente planteó un evento.
por ejemplo:
public event Action<bool, int, Blah> DiagnosticsEvent;
vs
public event EventHandler<DiagnosticsArgs> DiagnosticsEvent;
class DiagnosticsArgs : EventArgs
{
public DiagnosticsArgs(bool b, int i, Blah bl)
{...}
...
}
el uso sería casi el mismo en ambos casos:
obj.DiagnosticsEvent += HandleDiagnosticsEvent;
Hay varias cosas que no me gustan del event EventHandler<>
pattern:
- Declaración de tipo adicional derivada de EventArgs
- Paso obligatorio de la fuente del objeto: a menudo a nadie le importa
Más código significa más código para mantener sin ninguna ventaja clara.
Como resultado, prefiero event Action<>
Sin embargo, solo si hay demasiados argumentos de tipo en Acción <>, entonces se requeriría una clase adicional.
En cuanto a los patrones de eventos Standard .NET , encontramos
La firma estándar para un delegado de evento .NET es:
void OnEventRaised(object sender, EventArgs args);
[...]
La lista de argumentos contiene dos argumentos: el emisor y los argumentos del evento. El tipo de emisor de tiempo de compilación es System.Object, aunque es probable que conozca un tipo más derivado que siempre sería correcto. Por convención, usa el objeto .
A continuación, en la misma página, encontramos un ejemplo de la definición típica de evento que es algo así como
public event EventHandler<EventArgs> EventName;
Si hubiéramos definido
class MyClass
{
public event Action<MyClass, EventArgs> EventName;
}
el controlador podría haber sido
void OnEventRaised(MyClass sender, EventArgs args);
donde el sender
tiene el tipo correcto ( más derivado ).
En la mayoría de los casos, yo diría que sigan el patrón. Me he desviado de él, pero muy raramente, y por razones específicas. En el caso en cuestión, el mayor problema que tendría es que probablemente aún use un Action<SomeObjectType>
, lo que me permitirá agregar propiedades extra más adelante, y usar la propiedad bidireccional ocasional (piense en Handled
, u otro feedback-events donde el suscriptor necesita establecer una propiedad en el objeto del evento). Y una vez que haya comenzado a recorrer esa línea, también podría usar EventHandler<T>
para algo de T
La principal diferencia será que si usa Action<>
su evento no seguirá el patrón de diseño de prácticamente ningún otro evento en el sistema, lo que consideraría un inconveniente.
Una ventaja con el patrón de diseño dominante (aparte del poder de igualdad) es que puede extender el objeto EventArgs
con nuevas propiedades sin alterar la firma del evento. Esto aún sería posible si usó Action<SomeClassWithProperties>
, pero realmente no veo el punto de no usar el enfoque regular en ese caso.
La ventaja de un enfoque más detallado viene cuando su código está dentro de un proyecto de 300,000 líneas.
Usando la acción, como lo has hecho, no hay forma de decirme qué son bool, int y Blah. Si su acción pasó un objeto que definió los parámetros, entonces está bien.
Usando un manejador de sucesos que quería un EventArgs y si completaba su ejemplo de DiagnosticsArgs con getters para las propiedades que comentaban su propósito, entonces su aplicación sería más comprensible. Además, comente o asigne un nombre completo a los argumentos en el constructor DiagnosticsArgs.
Si sigue el patrón de evento estándar, puede agregar un método de extensión para hacer que la verificación del disparo del evento sea más segura / más fácil. (es decir, el siguiente código agrega un método de extensión llamado SafeFire () que hace la comprobación nula, y (obviamente) copia el evento en una variable separada para estar a salvo de la condición de carrera nula habitual que puede afectar a los eventos).
(Aunque estoy en cierto modo dividido si debería usar métodos de extensión en objetos nulos ...)
public static class EventFirer
{
public static void SafeFire<TEventArgs>(this EventHandler<TEventArgs> theEvent, object obj, TEventArgs theEventArgs)
where TEventArgs : EventArgs
{
if (theEvent != null)
theEvent(obj, theEventArgs);
}
}
class MyEventArgs : EventArgs
{
// Blah, blah, blah...
}
class UseSafeEventFirer
{
event EventHandler<MyEventArgs> MyEvent;
void DemoSafeFire()
{
MyEvent.SafeFire(this, new MyEventArgs());
}
static void Main(string[] args)
{
var x = new UseSafeEventFirer();
Console.WriteLine("Null:");
x.DemoSafeFire();
Console.WriteLine();
x.MyEvent += delegate { Console.WriteLine("Hello, World!"); };
Console.WriteLine("Not null:");
x.DemoSafeFire();
}
}