Uso de verificación nula en el controlador de eventos

Al verificar si un controlador de eventos es nulo, ¿se hace esto por subprocesos?

Asegurar que alguien esté escuchando el evento se hace así:

EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen); 

Si agrego el código siguiendo este patrón de arriba, donde verifico nulo, entonces ¿por qué necesitaría una verificación nula ( código tomado de este sitio )? ¿Qué me estoy perdiendo?

Además, ¿cuál es la regla con eventos y GC?

Realmente no está claro a qué te refieres, me temo, pero si existe la posibilidad de que el delegado sea nulo, debes verificarlo por separado en cada tema. Normalmente harías:

 public void OnSeven() { DivBySevenHandler handler = EventSeven; if (handler != null) { handler(...); } } 

Esto garantiza que incluso si EventSeven cambia durante el curso de OnSeven() no obtendrá una NullReferenceException .

Pero tienes razón en que no necesitas el cheque nulo si definitivamente tienes un controlador suscrito. Esto se puede hacer fácilmente en C # 2 con un manejador “no operativo”:

 public event DivBySevenHandler EventSeven = delegate {}; 

Por otro lado, es posible que desee algún tipo de locking solo para asegurarse de que tiene el “último” conjunto de controladores, si puede obtener suscripciones de varios subprocesos. Tengo un ejemplo en mi tutorial de subprocesos que puede ayudar, aunque normalmente recomiendo evitarlo.

En términos de recolección de basura, el editor del evento termina con una referencia al suscriptor del evento (es decir, el objective del controlador). Esto es solo un problema si el editor debe vivir más tiempo que el suscriptor.

El problema es que si nadie se suscribe al evento, es nulo. Y no puedes invocar contra un nulo. Tres enfoques saltan a la mente:

  • verificar nulo (ver abajo)
  • agregue un controlador “no hacer nada”: public event EventHandler MyEvent = delegate {};
  • use un método de extensión (ver a continuación)

Cuando se busca nulo, para que sea seguro para subprocesos, en teoría debe capturar primero la referencia de delegado (en caso de que cambie entre el cheque y la invocación):

 protected virtual void OnMyEvent() { EventHandler handler = MyEvent; if(handler != null) handler(this, EventArgs.Empty); } 

Los métodos de extensión tienen la propiedad inusual de que se pueden llamar en instancias nulas …

  public static void SafeInvoke(this EventHandler handler, object sender) { if (handler != null) handler(sender, EventArgs.Empty); } public static void SafeInvoke(this EventHandler handler, object sender, T args) where T : EventArgs { if (handler != null) handler(sender, args); } 

entonces puedes llamar:

 MyEvent.SafeInvoke(this); 

y es a la vez seguro (a través del cheque) y seguro para subprocesos (leyendo la referencia una sola vez).

Estoy cavando una publicación muy antigua, pero solo quiero agregar información breve sobre la syntax de C # 6.0:

Ahora es posible reemplazar esto:

 var handler = EventSeven; if (handler != null) handler.Invoke(this, EventArgs.Empty); 

con este:

 handler?.Invoke(this, EventArgs.Empty); 

EDITAR: Combinándolo con miembros con cuerpo de expresión , puede acortar el siguiente código:

 protected virtual void OnMyEvent() { EventHandler handler = MyEvent; handler?.Invoke(this, EventArgs.Empty); } 

hasta un trazador de líneas:

 protected virtual void OnMyEvent() => MyEvent?.Invoke(this, EventArgs.Empty); 

Vea MSDN para más información sobre el operador nulo-condicional
Mira este blog sobre miembros con cuerpo de expresión

Siempre es una buena práctica verificar un controlador de eventos antes de dispararlo. Hago esto incluso si inicialmente me “garantizo” que siempre está configurado. Si luego cambio esto, no es necesario que verifique todo el disparo de mi evento. Entonces, para cada evento siempre tengo un método OnXXX de acompañamiento como este:

 private void OnEventSeven() { var handler = EventSeven; if (handler != null) { handler(this, EventArgs.Empty); } } 

Esto es especialmente importante si el controlador de eventos es público para su clase, ya que los llamadores externos pueden agregar y eliminar manejadores de eventos a voluntad.

Si te refieres a esto:

 public static void OnEventSeven(DivBySevenEventArgs e) { if(EventSeven!=null) EventSeven(new object(),e); } 

pieza de código, entonces la respuesta es:

Si nadie se suscribe al controlador de eventos “EventSeven”, obtendrá una excepción de referencia nula en “EventSeven (new object (), e);”

Y la regla:

El suscriptor es responsable de agregar un controlador (+ =) y eliminarlo (- =) cuando ya no desea recibir los eventos. La recolección de basura se rige por las reglas predeterminadas, si un objeto ya no se referencia, se puede limpiar.

Usando PostSharp es posible ajustar el ensamblado comstackdo en un paso posterior a la comstackción. Esto le permite aplicar ‘aspectos’ al código, resolviendo problemas transversales.

Aunque las comprobaciones nulas o la inicialización de delegado vacío pueden ser un problema menor, escribí un aspecto que lo resuelve agregando un delegado vacío a todos los eventos en un ensamblado.

Su uso es bastante fácil:

 [assembly: InitializeEventHandlers( AttributeTargetTypes = "Main.*" )] namespace Main { ... } 

Discutí el aspecto en detalle en mi blog . En caso de que tengas PostSharp, este es el aspecto:

 ///  /// Aspect which when applied on an assembly or class, initializes all the event handlers () members /// in the class(es) with empty delegates to prevent 's. ///  /// Steven Jeuris [AttributeUsage( AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Event )] [MulticastAttributeUsage( MulticastTargets.Event, AllowMultiple = false )] [AspectTypeDependency( AspectDependencyAction.Commute, typeof( InitializeEventHandlersAttribute ) )] [Serializable] public class InitializeEventHandlersAttribute : EventLevelAspect { [NonSerialized] Action _addEmptyEventHandler; [OnMethodEntryAdvice, MethodPointcut( "SelectConstructors" )] public void OnConstructorEntry( MethodExecutionArgs args ) { _addEmptyEventHandler( args.Instance ); } // ReSharper disable UnusedMember.Local IEnumerable SelectConstructors( EventInfo target ) { return target.DeclaringType.GetConstructors( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ); } // ReSharper restre UnusedMember.Local public override void RuntimeInitialize( EventInfo eventInfo ) { base.RuntimeInitialize( eventInfo ); // Construct a suitable empty event handler. MethodInfo delegateInfo = DelegateHelper.MethodInfoFromDelegateType( eventInfo.EventHandlerType ); ParameterExpression[] parameters = delegateInfo.GetParameters().Select( p => Expression.Parameter( p.ParameterType ) ).ToArray(); Delegate emptyDelegate = Expression.Lambda( eventInfo.EventHandlerType, Expression.Empty(), "EmptyDelegate", true, parameters ).Compile(); // Create a delegate which adds the empty handler to an instance. _addEmptyEventHandler = instance => eventInfo.AddEventHandler( instance, emptyDelegate ); } } 

… y el método de ayuda que utiliza:

 ///  /// The name of the Invoke method of a Delegate. ///  const string InvokeMethod = "Invoke"; ///  /// Get method info for a specified delegate type. ///  /// The delegate type to get info for. /// The method info for the given delegate type. public static MethodInfo MethodInfoFromDelegateType( Type delegateType ) { Contract.Requires( delegateType.IsSubclassOf( typeof( MulticastDelegate ) ), "Given type should be a delegate." ); return delegateType.GetMethod( InvokeMethod ); }