这是在C#中没有空检查的情况下触发/调用事件的更好方法吗?

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

public class MyExample { public event Action MyEvent; // could be an event EventHandler, 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 } } 

任何人都可以解释为什么不这样做的原因? (亲与利弊)

优点和缺点是:

  • 空检查非常便宜; 我们谈论的是十亿分之一秒。 在对象的整个生命周期中分配一个委托然后垃圾收集失败可能需要花费几百万分之一秒。 另外,你消耗了几十个字节的内存。 此外,每次事件触发时,您都会对不执行任何操作的方法进行不必要的调用,从而消耗更多的微秒。 如果你是那种关心百万分之一秒和几十个字节的人,那么这可能是一个有意义的差异; 对于绝大多数情况,它不会。

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

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

这肯定是一种风格的东西; 但是,我认为大多数开发人员在进行空检查时都会考虑安全性。 这个世界上没有任何东西可以保证一个bug不会蔓延到系统中,并且无需进行空检查。

它仍然可以为null。 考虑:

  var x = new MyExample(); x.MyEvent += SomeHandler; // ... later, when the above code is disposed of x.MyEvent -= SomeHandler; 

编辑:实际上,我把它拿回来。 测试过此后,如果您使用匿名委托设置处理程序,则看起来不能清除它,因此您可以保存空检查。

我不确定这是否是可靠的行为,或者只是语言实现的工件……

我在简化事件触发时看到的最佳权衡是增加了一个扩展方法。 请参阅使用扩展方法提升C#事件 – 这是不是很糟糕? 。