13

我见过的大多数代码都使用以下方式来声明和调用事件触发:

public class MyExample
{
    public event Action MyEvent; // could be an event EventHandler<EventArgs>, too

    private void OnMyEvent()
    {
        var handler = this.MyEvent; // copy before access (to aviod race cond.)
        if (handler != null)
        {
            handler();
        }
    }

    public void DoSomeThingsAndFireEvent() 
    {
        // ... doing some things here
        OnMyEvent();
    }
 }

甚至 ReSharper 也会以上述方式生成调用方法。

为什么不这样做:

public class MyExample
{
    public event Action MyEvent = delegate {}; // init here, so it's never null

    public void DoSomeThingsAndFireEvent() 
    {
        // ... doing some things here
        OnMyEvent(); // save to call directly because this can't be null
    }
 }

谁能解释为什么不这样做的原因?(优点与缺点)

4

4 回答 4

19

优点和缺点是:

  • 空检查非常便宜;我们说的是十亿分之一秒。分配一个委托,然后在对象的剩余生命周期内不成功地对其进行垃圾收集可能需要百万分之几秒以上的时间。此外,您还消耗了数十个字节的内存。另外,每次事件触发时,都会对一个什么都不做的方法进行不必要的调用,从而消耗更多微秒。如果您是那种关心百万分之一秒和几十个字节的人,那么这可能是一个有意义的差异;对于绝大多数情况,它不会。

  • 您必须记住始终创建空委托。这真的比记住检查空值更容易吗?

  • 这两种模式实际上都没有使事件线程安全。这两种模式仍然完全有可能在一个线程上触发事件处理程序,同时在另一个线程上删除,这意味着它们会竞争。如果您的处理程序删除代码破坏了处理程序所需的状态,则可能一个线程正在破坏该状态,而另一个线程正在运行该处理程序。不要认为仅仅检查 null 或分配一个空的处理程序就可以神奇地消除竞争条件。它只会消除导致取消引用 null 的竞争条件。

于 2012-04-23T14:51:20.733 回答
7

这肯定是一种风格。但是,我认为大多数开发人员在进行空检查时会追求安全而不是风格。 这个世界上没有任何东西可以保证错误不会蔓延到系统中,并且空值检查将继续是不必要的。

于 2012-04-23T14:09:13.287 回答
6

它仍然可以为空。考虑:

    var x = new MyExample();
    x.MyEvent += SomeHandler;

    // ... later, when the above code is disposed of

    x.MyEvent -= SomeHandler;

编辑:实际上,我收回了它。对此进行了测试,如果您使用匿名委托来设置处理程序,看起来您无法清除它,因此您可以保存空检查。

我不确定这是否是可靠的行为,或者只是语言实现的产物......

于 2012-04-23T14:05:30.860 回答
3

我见过的简化事件触发的最佳权衡是添加扩展方法。请参阅使用扩展方法引发 C# 事件 - 这很糟糕吗?.

于 2012-04-23T14:35:42.670 回答