在事件处理程序中使用空检查
当检查一个事件处理程序是否为null时,是在每个线程的基础上完成的?
确保有人正在听这个事件是这样做的:
EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen);
如果我在上面的这个模式中添加代码,在那里我检查null,那么为什么我需要一个空的检查( 代码取自这个网站 )。 我错过了什么?
另外,事件和GC的规则是什么?
恐怕真的不清楚你的意思,但是如果委托为空的可能性,你需要在每个线程上单独检查。 通常你会这样做:
public void OnSeven() { DivBySevenHandler handler = EventSeven; if (handler != null) { handler(...); } }
这确保即使EventSeven
在OnSeven()
过程中发生更改,您也不会得到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 ); }