为什么事件需要至少有一个处理程序?
我为我的Control创建了自定义事件,并在我控制的代码内部,我称之为事件:
this.MyCustomEvent(this, someArgs);
如果没有订阅它的处理程序,它会抛出NullReferenceException
。
当我在控件的构造函数中添加一个处理程序时,一切正常:
this.MyCustomEvent += myCutomEventHandler;
void myCustomEventHandler(object sender, EventArgs e)
{ /* do nothing */ }
这是正常的还是我做错了什么? 如果有任何处理程序订阅,它不应该自动检查吗?这有点蠢,恕我直言。
答案 0 :(得分:4)
请注意,委托是引用类型,其默认值为null。
其他人提出的解决方案,即在触发事件之前检查null,不是线程安全的,因为侦听器可以取消订阅null检查和触发事件之间的事件。 我已经看到了涉及将委托复制到局部变量并在触发前将其检查为null的解决方案,例如
EventHandler myCustomEventCopy = MyCustomEvent;
if (myCustomEventCopy != null)
{
myCustomEventCopy (this, someArgs);
}
但这有一个竞争条件,即即使在取消订阅事件后处理程序可能会触发,这可能会破坏申请状态。
处理这两个问题的一个解决方案是将事件初始化为空白处理程序,例如
public event EventHandler MyCustomEvent = delegate { };
然后在没有任何检查的情况下将它们关闭,例如
MyCustomEvent(this, someArgs);
编辑:正如其他人所指出的,这是一个复杂的问题。
http://blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx
Lippert指出完全删除“撤销注册后触发器处理”问题需要处理程序本身以健壮的方式编写。
答案 1 :(得分:4)
我建议你有一个非常有用的扩展方法:
public static void Raise<T>(this EventHandler<T> eventHandler, object sender, T e) where T : EventArgs
{
if (eventHandler != null)
{
eventHandler(sender, e);
}
}
将为您进行检查。
用法:
MyCustomEvent.Raise(this, EventArgs.Empty);
答案 2 :(得分:2)
底部的事件是MulticastDelegate
,如果调用列表中没有方法,则该事件为null。通常,您使用RaiseEvent()
方法来调用事件,模式如下所示:
public void RaiseEvent()
{
var handler = MyEvent;
if(handler != null)
handler(this, new EventArgs());
}
在将事件分配给变量时,它是线程安全的。您可以错过已删除或添加的方法,它是在原子操作之间添加的(赋值 - &gt; null check - &gt;调用)。
答案 3 :(得分:2)
这是正常行为。在.NET中抛出事件的传统模式是使用一个名为OnMyCustomEvent
的方法来抛出事件,如下所示:
protected void OnMyCustomEvent(MyCustomEventArgs e)
{
EventHandler<MyCustomEventArgs> threadSafeCopy = MyCustomEvent;
if (threadSafeCopy != null)
threadSafeCopy(this, e);
}
public event EventHandler<MyCustomEventArgs> MyCustomEvent;
然后,从您的代码中,您可以调用this.OnMyCustomEvent(someArgs)
。
答案 4 :(得分:1)
这是正常行为。为了避免这种情况,请在调用之前测试是否有事件订阅者:
if (MyCustomEvent != null)
{
MyCustomEvent(this, someArgs);
}
答案 5 :(得分:1)
复制引用然后检查null(如果没有附加处理程序则引用的值)有“标准”方法 - 由其他答案给出:
public EventHandler<MyEventArgs> MyEvent;
protected virtual OnMyEvent(MyEventArgs args) {
var copy = MyEvent;
if (copy != null) {
copy(this, args);
}
}
这是有效的,因为在某种程度上,MulticastDelegate
实例是不可变的。
还有另一种方法:空对象模式:
public EventHandler<MyEventArgs> MyEvent;
// In constructor:
MyEvent += (s,e) => {}; // No op, so it is always initialised
并且不需要复制或检查null,因为它不会。这是有效的,因为Clear
上没有event
方法。
答案 6 :(得分:0)
是的,您需要检查是否为null。事实上,有一种正确的方法:
var myEvent = MyCustomEvent;
if (myEvent != null)
myEvent(this, someArgs);
这是正式的正确方法,因为它可以在空检后但在通话之前更改事件时避免可能的竞争条件。