c# - que - ¿Hay algún inconveniente en agregar un delegado vacío anónimo en la declaración de evento?
que es un evento en programacion (9)
He visto algunas menciones de este modismo (incluso en SO ):
// Deliberately empty subscriber
public event EventHandler AskQuestion = delegate {};
El lado positivo es claro: evita la necesidad de buscar nulos antes de plantear el evento.
Sin embargo, estoy dispuesto a entender si hay inconvenientes. Por ejemplo, ¿es algo que se usa ampliamente y es lo suficientemente transparente como para no causar dolores de cabeza por mantenimiento? ¿Hay algún golpe de rendimiento apreciable de la llamada al suscriptor del evento vacío?
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 haces:
try
{
MyEvent(this, EventArgs.Empty);
}
catch
{
}
Pero, si tiene varios suscriptores y un suscriptor lanza una excepción, ¿qué ocurre con los otros suscriptores?
Para ese fin, he estado usando algunos métodos de ayuda estáticos que hacen la comprobación nula y se traga 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
{ }
}
}
El único inconveniente es una penalización de rendimiento muy leve ya que está llamando delegado extra vacío. Aparte de eso, no hay penalización por mantenimiento u otro inconveniente.
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 tendrá que volver a realizar otra prueba de evento nulo:
// Works, even for null events.
MyButtonClick.Raise(this, EventArgs.Empty);
Entiendo que el delegado vacío es seguro para hilos, mientras que el cheque nulo no lo es.
No hay una penalización de rendimiento significativa para hablar, excepto, posiblemente, para algunas situaciones extremas.
Sin embargo, tenga en cuenta que este truco se vuelve menos relevante en C # 6.0, ya que el lenguaje proporciona una sintaxis alternativa a los delegados llamantes que pueden ser nulos:
delegateThatCouldBeNull?.Invoke(this, value);
Arriba, ?.
operador condicional nulo ?.
combina la comprobación nula con una invocación condicional.
Para sistemas que hacen un uso intensivo de eventos y son críticos para el rendimiento , definitivamente querrás al menos considerar no hacer esto. El costo de generar un evento con un delegado vacío es aproximadamente el doble que el de generar 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 utilicé 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);
}
}
}
}
Si lo está haciendo a / lot /, es posible que desee tener un único delegado vacío, estático / compartido que reutilice, simplemente para reducir el volumen de instancias de delegado. Tenga en cuenta que el compilador guarda 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.
Una cosa se pierde como respuesta a esta pregunta hasta el momento: 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 cosa 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, escribe el cheque si.
Espero que ayude ;)
pd: gracias por el cálculo del rendimiento.
pps: editado desde un caso de evento a 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 todavía se ajusta al hilo ... pero creo que es un principio importante. Por favor, también verifique otro hilo de stackflow