He visto algunas menciones de este idioma (incluso en SO ):
// Deliberately empty subscriber
public event EventHandler AskQuestion = delegate {};
La ventaja es clara: evita la necesidad de verificar el valor nulo antes de generar el evento.
Sin embargo, estoy ansioso por comprender si hay alguna desventaja. Por ejemplo, ¿es algo de uso generalizado y lo suficientemente transparente como para no causar un dolor de cabeza por mantenimiento? ¿Hay algún impacto apreciable en el rendimiento de la llamada vacía del suscriptor al evento?
En lugar de inducir una sobrecarga de rendimiento, ¿por qué no utilizar un método de extensión para aliviar ambos problemas?
public static void Raise(this EventHandler handler, object sender, EventArgs e) { if(handler != null) { handler(sender, e); } }
Una vez definido, nunca más tendrá que hacer otra verificación de evento nulo:
// Works, even for null events. MyButtonClick.Raise(this, EventArgs.Empty);
fuente
handler
anterior es un parámetro de valor para el método. No cambiará mientras se esté ejecutando el método. Para que eso ocurra, debería ser unref
parámetro (obviamente no está permitido que un parámetro tenga tantoref
elthis
modificador como el), o un campo, por supuesto.Para los sistemas que hacen un uso intensivo de eventos y son críticos para el rendimiento , definitivamente querrá al menos considerar no hacer esto. El costo de generar un evento con un delegado vacío es aproximadamente el doble que el de generarlo con un cheque nulo primero.
Aquí hay algunas cifras que ejecutan puntos de referencia en mi máquina:
For 50000000 iterations . . . No null check (empty delegate attached): 530ms With null check (no delegates attached): 249ms With null check (with delegate attached): 452ms
Y aquí está el código que usé para obtener estas cifras:
using System; using System.Diagnostics; namespace ConsoleApplication1 { class Program { public event EventHandler<EventArgs> EventWithDelegate = delegate { }; public event EventHandler<EventArgs> EventWithoutDelegate; static void Main(string[] args) { //warm up new Program().DoTimings(false); //do it for real new Program().DoTimings(true); Console.WriteLine("Done"); Console.ReadKey(); } private void DoTimings(bool output) { const int iterations = 50000000; if (output) { Console.WriteLine("For {0} iterations . . .", iterations); } //with anonymous delegate attached to avoid null checks var stopWatch = Stopwatch.StartNew(); for (var i = 0; i < iterations; ++i) { RaiseWithAnonDelegate(); } stopWatch.Stop(); if (output) { Console.WriteLine("No null check (empty delegate attached): {0}ms", stopWatch.ElapsedMilliseconds); } //without any delegates attached (null check required) stopWatch = Stopwatch.StartNew(); for (var i = 0; i < iterations; ++i) { RaiseWithoutAnonDelegate(); } stopWatch.Stop(); if (output) { Console.WriteLine("With null check (no delegates attached): {0}ms", stopWatch.ElapsedMilliseconds); } //attach delegate EventWithoutDelegate += delegate { }; //with delegate attached (null check still performed) stopWatch = Stopwatch.StartNew(); for (var i = 0; i < iterations; ++i) { RaiseWithoutAnonDelegate(); } stopWatch.Stop(); if (output) { Console.WriteLine("With null check (with delegate attached): {0}ms", stopWatch.ElapsedMilliseconds); } } private void RaiseWithAnonDelegate() { EventWithDelegate(this, EventArgs.Empty); } private void RaiseWithoutAnonDelegate() { var handler = EventWithoutDelegate; if (handler != null) { handler(this, EventArgs.Empty); } } } }
fuente
Si lo está haciendo mucho, es posible que desee tener un delegado vacío único, estático / compartido que reutilice, simplemente para reducir el volumen de instancias de delegado. Tenga en cuenta que el compilador almacena en caché este delegado por evento de todos modos (en un campo estático), por lo que es solo una instancia de delegado por definición de evento, por lo que no es un gran ahorro, pero tal vez valga la pena.
El campo por instancia en cada clase seguirá ocupando el mismo espacio, por supuesto.
es decir
internal static class Foo { internal static readonly EventHandler EmptyEvent = delegate { }; } public class Bar { public event EventHandler SomeEvent = Foo.EmptyEvent; }
Aparte de eso, parece estar bien.
fuente
No hay una penalización de desempeño significativa de la que hablar, excepto, posiblemente, en algunas situaciones extremas.
Sin embargo, tenga en cuenta que este truco se vuelve menos relevante en C # 6.0, porque el lenguaje proporciona una sintaxis alternativa para llamar a los delegados que puede ser nula:
delegateThatCouldBeNull?.Invoke(this, value);
Arriba, el operador condicional nulo
?.
combina la verificación nula con una invocación condicional.fuente
Tengo entendido que el delegado vacío es seguro para subprocesos, mientras que la verificación nula no lo es.
fuente
Yo diría que es una construcción un poco peligrosa, porque te tienta a hacer algo como:
MyEvent(this, EventArgs.Empty);
Si el cliente lanza una excepción, el servidor la acompaña.
Entonces, tal vez lo hagas:
try { MyEvent(this, EventArgs.Empty); } catch { }
Pero, si tiene varios suscriptores y un suscriptor lanza una excepción, ¿qué sucede con los otros suscriptores?
Con ese fin, he estado usando algunos métodos auxiliares estáticos que hacen la verificación nula y se tragan cualquier excepción del lado del suscriptor (esto es de idesign).
// Usage EventHelper.Fire(MyEvent, this, EventArgs.Empty); public static void Fire(EventHandler del, object sender, EventArgs e) { UnsafeFire(del, sender, e); } private static void UnsafeFire(Delegate del, params object[] args) { if (del == null) { return; } Delegate[] delegates = del.GetInvocationList(); foreach (Delegate sink in delegates) { try { sink.DynamicInvoke(args); } catch { } } }
fuente
Try.TryAction()
función útil , no untry{}
bloque explícito ), pero no ignoro las excepciones, lasEn lugar del enfoque de "delegado vacío", se puede definir un método de extensión simple para encapsular el método convencional de verificar el controlador de eventos contra nulo. Se describe aquí y aquí .
fuente
Hasta ahora, una cosa se ha perdido como respuesta a esta pregunta: es peligroso evitar la verificación del valor nulo .
public class X { public delegate void MyDelegate(); public MyDelegate MyFunnyCallback = delegate() { } public void DoSomething() { MyFunnyCallback(); } } X x = new X(); x.MyFunnyCallback = delegate() { Console.WriteLine("Howdie"); } x.DoSomething(); // works fine // .. re-init x x.MyFunnyCallback = null; // .. continue x.DoSomething(); // crashes with an exception
La cuestión es: nunca se sabe quién usará su código de qué manera. Nunca se sabe, si en algunos años durante una corrección de errores de su código, el evento / controlador se establece en nulo.
Siempre, escriba el cheque si.
Espero que ayude ;)
ps: Gracias por el cálculo de rendimiento.
pps: lo editó desde un caso de evento hasta un ejemplo de devolución de llamada. Gracias por los comentarios ... "Codifiqué" el ejemplo sin Visual Studio y ajusté el ejemplo que tenía en mente a un evento. Perdón por la confusion.
ppps: No sé si aún encaja en el hilo ... pero creo que es un principio importante. Compruebe también otro hilo de stackflow
fuente
protected
. Solo puede separar el evento de la propia clase (o de una clase derivada).