代码之家  ›  专栏  ›  技术社区  ›  Gacek

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

  •  2
  • Gacek  · 技术社区  · 16 年前

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

    我为我的控件创建了自定义事件,在控件代码的某个地方,我将此事件称为:

    this.MyCustomEvent(this, someArgs);
    

    它抛出一个 NullReferenceException 如果没有处理程序订阅它。

    this.MyCustomEvent += myCutomEventHandler;
    
    void myCustomEventHandler(object sender, EventArgs e)
    { /* do nothing */ }
    

    这是正常的还是我做错了什么? 它不应该自动检查是否有任何处理程序订阅吗?有点傻,伊莫。

    7 回复  |  直到 16 年前
        1
  •  3
  •   Ani    16 年前

    请注意,委托是引用类型,其默认值为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指出,完全删除“取消注册后触发处理程序”问题需要 处理程序 他们自己写得很好。

        2
  •  4
  •   abatishchev Karl Johan    16 年前

    我建议您使用一种非常有用的扩展方法:

    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);
    
        3
  •  2
  •   Femaref    16 年前

    底部的事件是 MulticastDelegate RaiseEvent() 方法调用事件,模式如下所示:

    public void RaiseEvent()
    {
      var handler = MyEvent;
      if(handler != null)
        handler(this, new EventArgs());
    }
    

    将事件分配给变量时,它是线程安全的。但是,您可能会丢失已删除或已添加的方法,该方法是在原子操作之间添加的(赋值->空检查->调用)。

        4
  •  2
  •   Dan Puzey    16 年前

    这是正常的行为。在.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) 相反。

        5
  •  1
  •   Darin Dimitrov    16 年前

    这是正常的行为。要避免此情况,请在调用事件订户之前始终测试它是否存在:

    if (MyCustomEvent != null)
    {
        MyCustomEvent(this, someArgs);
    }
    
        6
  •  1
  •   Richard    16 年前

    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
    

    Clear 方法 event .

        7
  •  0
  •   abatishchev Karl Johan    16 年前

    是的,您需要检查null。事实上,有一个正确的方法:

    var myEvent = MyCustomEvent;
    if (myEvent != null)
        myEvent(this, someArgs);