在事件处理程序中使用空检查

当检查一个事件处理程序是否为null时,是在每个线程的基础上完成的?

确保有人正在听这个事件是这样做的:

EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen); 

如果我在上面的这个模式中添加代码,在那里我检查null,那么为什么我需要一个空的检查( 代码取自这个网站 )。 我错过了什么?

另外,事件和GC的规则是什么?

恐怕真的不清楚你的意思,但是如果委托为空的可能性,你需要在每个线程上单独检查。 通常你会这样做:

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

这确保即使EventSevenOnSeven()过程中发生更改,您也不会得到NullReferenceException

但是你是对的,你不需要空检查,如果你肯定有一个订阅处理程序。 这可以很容易地在C#2中使用“no-op”处理程序完成:

 public event DivBySevenHandler EventSeven = delegate {}; 

另一方面,如果您可能从各个线程获得订阅,则可能需要某种locking来确保您拥有“最新”的处理程序集。 我在我的线程教程中有一个例子可以帮助 – 虽然通常我build议尽量避免要求。

在垃圾收集方面,事件发布者最终引用事件订阅者 (即处理的目标)。 如果发行人的寿命比用户长,这只是一个问题。

问题是如果没有人订阅这个事件,它是空的。 而且你不能针对null调用。 想到三种方法:

  • 检查为空(见下文)
  • 添加一个“什么都不做”的处理程序: public event EventHandler MyEvent = delegate {};
  • 使用扩展方法(见下文)

当检查null时,为了线程安全,你必须在理论上捕获委托引用(如果它在检查和调用之间改变):

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

扩展方法具有不寻常的属性,它们可以在空实例上调用。

  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); } 

那么你可以打电话给:

 MyEvent.SafeInvoke(this); 

它是无效的(通过检查)和线程安全的(通过只读一次参考)。

我正在挖掘一个非常旧的post – 但我只想附加一些关于C#6.0-Syntax的简短信息:

现在可以取代这个:

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

有了这个:

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

编辑:结合它与expression身体的成员 ,你可以缩短以下代码:

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

直到一行:

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

有关空条件运算符的更多信息,请参阅MSDN
看到这个博客关于expression体的成员

在启动之前检查一个事件处理程序总是很好的做法。 即使我最初“保证”自己始终设置,我也这样做。 如果我后来改变这个,我不必检查所有的事件发射。 所以对于每个事件,我总是有一个像这样的OnXXX方法:

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

如果事件处理程序对您的类是公共的,这是特别重要的,因为外部调用者可以随意添加和删除事件处理程序。

如果你的意思是:

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

一段代码,那么答案是:

如果没有人订阅“EventSeven”事件处理程序,那么您将在“EventSeven(new object(),e)”上得到空引用exception;

规则:

订阅者负责添加处理程序(+ =),并在不想再收到事件时删除( – =)。 垃圾收集按照默认规则进行,如果一个对象不再被引用,它可以被清除。

使用PostSharp可以在编译后的步骤中调整编译后的程序集。 这使您可以将“方面”应用于代码,从而解决横切问题。

虽然空检查或空委托初始化可能是一个非常小的问题,但我写了一个方面,通过向程序集中的所有事件添加空委托来解决此问题。

这个用法很简单:

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

我在博客上详细讨论了这个方面 。 如果你有PostSharp,这里是方面:

 /// <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 ); } } 

…以及它使用的辅助方法:

 /// <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 ); }