我见过的大多数代码都使用以下方式来声明和调用事件触发:
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
}
}
谁能解释为什么不这样做的原因?(优点与缺点)
优点和缺点是:
-
空支票非常便宜;我们说的是十亿分之一秒。分配一个委托,然后在对象的剩余生存期内对其进行垃圾回收失败,可能需要几百万分之一秒以上的时间。此外,您还会多消耗数十字节的内存。此外,每次触发事件时,您都会收到对不执行任何操作的方法的不必要调用,从而消耗更多的微秒。如果您是那种关心百万分之一秒和数十字节的人,那么这可能是一个有意义的差异;对于绝大多数情况,它不会。
-
您必须记住始终创建空委托。这真的比记住检查空更容易吗?
-
这两种模式实际上都没有使事件线程安全。在这两种模式中,事件处理程序仍然完全有可能在一个线程上触发,同时在另一个线程上被删除,这意味着它们会竞争。如果处理程序删除代码销毁处理程序所需的状态,则可能一个线程正在销毁该状态,而另一个线程正在运行处理程序。不要认为仅仅检查 null 或分配空处理程序就能神奇地消除争用条件。它仅消除了导致取消引用 null 的争用条件。
这肯定是一种风格; 但是,我认为大多数开发人员在涉及空检查时都追求安全而不是风格。 这个世界上没有什么可以保证错误不会蔓延到系统中,并且空检查将继续是不必要的。
它仍然可以为空。 考虑:
var x = new MyExample();
x.MyEvent += SomeHandler;
// ... later, when the above code is disposed of
x.MyEvent -= SomeHandler;
编辑:实际上,我把它收回来。 对此进行测试后,如果您使用匿名委托来设置处理程序,则看起来无法清除它,因此您可以保存 null 检查。
我不确定这是可靠的行为,还是只是语言实现的产物......
我见过的简化事件触发的最佳权衡是添加扩展方法。 请参阅使用扩展方法引发 C# 事件 - 它不好吗?。