事件是否需要至少有一个处理程序?

时间:2010-08-07 15:52:39

标签: c# .net events

为什么事件需要至少有一个处理程序?

我为我的Control创建了自定义事件,并在我控制的代码内部,我称之为事件:

this.MyCustomEvent(this, someArgs);
如果没有订阅它的处理程序,它会抛出NullReferenceException

当我在控件的构造函数中添加一个处理程序时,一切正常:

this.MyCustomEvent += myCutomEventHandler;

void myCustomEventHandler(object sender, EventArgs e)
{ /* do nothing */ }

这是正常的还是我做错了什么? 如果有任何处理程序订阅,它不应该自动检查吗?这有点蠢,恕我直言。

7 个答案:

答案 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);

这是正式的正确方法,因为它可以在空检后但在通话之前更改事件时避免可能的竞争条件。