2009-03-23 5 views
24

Al verificar si un controlador de eventos es nulo, ¿se hace por subprocesos?Uso de verificación nula en el controlador de eventos

Asegurar que alguien está escuchando el evento se lleva a cabo de esta manera:

EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen); 

Si añado siguiente código este patrón por encima de donde puedo comprobar nula, entonces ¿por qué necesitaría un cheque nulo (code taken from this site). ¿Qué me estoy perdiendo?

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

+3

Ver: http://www.dailycoding.com/Posts/avoiding_event__null_check.aspx para una explicación expandida. –

Respuesta

44

Realmente no está claro lo que quieres decir, me temo, pero si existe la posibilidad de que el delegado sea nulo, debes verificarlo por separado en cada hilo. Normalmente, usted haría:

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

Esto asegura que incluso si EventSeven cambios durante el transcurso de OnSeven() no obtendrá una NullReferenceException.

Pero tiene razón en que no necesita el cheque nulo si definitivamente tiene un controlador suscrito. Esto se puede hacer fácilmente en C# 2 con un "no-op" manejador:

public event DivBySevenHandler EventSeven = delegate {}; 

Por otro lado, se fuerza desea algún tipo de bloqueo sólo para asegurarse de que usted tiene la "última "conjunto de controladores, si puede obtener suscripciones de varios hilos. Tengo un example in my threading tutorial que puede ayudar, aunque normalmente recomiendo tratar de evitarlo.

En términos de la recogida de basura, el evento editor termina con una referencia al abonado evento (es decir, el objetivo de la handler). Esto es solo un problema si el editor debe vivir más tiempo que el suscriptor.

+0

(para el OP/list) en el último punto, esto es ** especialmente ** verdadero de los eventos estáticos (ya que el campo estático nunca es elegible para la recopilación). Los eventos estáticos se evitan por exactamente esta razón. –

+0

me pregunto si no habría habido ningún problema que tiene una categoría de EventDelegate de delegados que tendrían la misma semántica como delegado vacío/excepto/que la invocación de un EventDelegate nula se define explícitamente como nop. – supercat

+1

¿Qué pasa con 'EventSeven? .Invoke (....)'. ¿es seguro para subprocesos? –

2

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. Así que para cada evento siempre tengo un método OnXxx acompaña como esto:

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

Esto es especialmente importante si el controlador de eventos es pública a su clase ya que los llamantes externos pueden añadir y eliminar controladores de eventos a voluntad.

+2

Eso no es seguro para subprocesos. Si el último controlador cancela la suscripción después de que se haya evaluado el "si", podría terminar con una NullReferenceException. –

+0

Hombre, ¿cómo es que tantos de nosotros no hemos visto el hilo? Seguridad de las Naciones Unidas en esto. ¡Gracias por la llamada despertador! –

+0

Parece que el mismo patrón (anti) está siendo utilizado por muchos de nosotros. Pero en este caso, Peter está haciendo lo mismo que Marc (capturando la referencia, pero usando un tipo implícito). ¿Cuál es la diferencia entre lo que Marc escribió y Peter? – dotnetdev

0

Si se refiere 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" entonces obtendrá una excepción nula en referencia en "EventSeven (nuevo objeto(), e);"

Y la regla:

El suscriptor es responsable de añadir un controlador (+ =) y retirarlo (- =) cuando él no quiere recibir los eventos más tiempo. La recolección de basura se rige por las reglas predeterminadas, si un objeto ya no se referencia, se puede limpiar.

+3

Eso no es seguro para subprocesos. Si el último controlador cancela la suscripción después de que se haya evaluado el "si", podría terminar con una NullreferenceException. –

+0

Es cierto ... este ejemplo está por todas partes (y yo también lo uso), ¡nunca me quedé quieto al hilo de seguridad de esto! – thijs

+2

Como un lado, ¿no es convencional que el remitente sea el objeto que inicia el evento (es decir, 'este'), en lugar de un objeto recién creado al azar? –

47

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:

  • cheque por nula (ver más abajo)
  • añadir un "no hacer nada" manejador: public event EventHandler MyEvent = delegate {};
  • usar un método de extensión (véase más adelante)

Al comprobar por nula, para ser seguro para subprocesos, debe en teoría capturar la referencia delegado primera (en caso de que cambia 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 que son exigibles en los casos nulos ...

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

entonces usted puede llamar:

MyEvent.SafeInvoke(this); 

y es tanto nula de fallos (a través de la comprobación) y seguro para subprocesos (leyendo la referencia una sola vez).

+2

como el hilo de seguridad a través de la copia implícita en la pila – ShuggyCoUk

+0

Buena explicación. Cuando dice "leyendo ... solo", eso es seguro para subprocesos, porque una vez que se realiza la lectura, el objetivo (objeto que se lee - referencia), puede cambiar libremente. ¿Pero cómo "lees la referencia una vez"? Quizás simplemente no seguí algo ... – dotnetdev

+1

@dotnetdev - porque lo estamos pasando como un argumento de método; lee el valor actual * en la pila *. Dentro de SafeInvoke, solo ve la copia ahora en la pila (el original se puede actualizar 200 veces, nunca lo veremos). Ayuda aquí que los delegados son inmutables, por lo que los cambios como ... –

0

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

Aunque las comprobaciones nulas o la inicialización de un 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 ensamblaje.

Su uso es muy fácil:

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

I discussed the aspect in detail on my blog. En caso de que tenga PostSharp, aquí está el aspecto:

/// <summary> 
/// Aspect which when applied on an assembly or class, initializes all the event handlers (<see cref="MulticastDelegate" />) members 
/// in the class(es) with empty delegates to prevent <see cref="NullReferenceException" />'s. 
/// </summary> 
/// <author>Steven Jeuris</author> 
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Event)] 
[MulticastAttributeUsage(MulticastTargets.Event, AllowMultiple = false)] 
[AspectTypeDependency(AspectDependencyAction.Commute, typeof(InitializeEventHandlersAttribute))] 
[Serializable] 
public class InitializeEventHandlersAttribute : EventLevelAspect 
{ 
    [NonSerialized] 
    Action<object> _addEmptyEventHandler; 


    [OnMethodEntryAdvice, MethodPointcut("SelectConstructors")] 
    public void OnConstructorEntry(MethodExecutionArgs args) 
    { 
     _addEmptyEventHandler(args.Instance); 
    } 

    // ReSharper disable UnusedMember.Local 
    IEnumerable<ConstructorInfo> SelectConstructors(EventInfo target) 
    { 
     return target.DeclaringType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 
    } 
    // ReSharper restore 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:

/// <summary> 
/// The name of the Invoke method of a Delegate. 
/// </summary> 
const string InvokeMethod = "Invoke"; 


/// <summary> 
/// Get method info for a specified delegate type. 
/// </summary> 
/// <param name = "delegateType">The delegate type to get info for.</param> 
/// <returns>The method info for the given delegate type.</returns> 
public static MethodInfo MethodInfoFromDelegateType(Type delegateType) 
{ 
    Contract.Requires(delegateType.IsSubclassOf(typeof(MulticastDelegate)), "Given type should be a delegate."); 

    return delegateType.GetMethod(InvokeMethod); 
} 
18

estoy cavando a cabo un post muy viejo - pero sólo quiero anexe información breve sobre C# 6.0-Sintaxis:

Ahora es posible sustituir este:

var handler = EventSeven; 

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

con esto:

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


EDIT: combinación con miembros de expresión cuerpo, se puede acortar el siguiente código:

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

abajo a una sola línea:

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


See MSDN for more information about the null-conditional operator
Ver this blog sobre los miembros de expresión de cuerpo

+0

Estoy tratando de entender si el 'MyEvent? .Invoke' es tan seguro como' manejador? .Invoke'. ¿La verificación de propagación nula es atómica? – Teejay

+1

En realidad, parece que sí. https://codeblog.jonskeet.uk/2015/01/30/clean-event-handlers-invocation-with-c-6/ – Teejay

Cuestiones relacionadas