更新
从C#6开始,此问题的答案是:
SomeEvent?.Invoke(this, e);
我经常听到/阅读以下建议:
在检查null
并触发事件之前,请务必复制事件。这将消除潜在的线程问题,即事件null
在您检查空值和触发事件的位置之间的位置变为:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
更新:我从阅读有关优化的内容中认为,这可能还要求事件成员具有可变性,但是Jon Skeet在回答中指出CLR不会优化副本。
但是,与此同时,为了使此问题发生,另一个线程必须执行以下操作:
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
实际的顺序可能是这种混合:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
关键在于OnTheEvent
作者取消订阅之后,但是他们只是专门取消订阅以避免这种情况的发生。当然,真正需要的是在add
和中remove
访问。此外,如果在触发事件时持有锁,则可能会出现死锁。
那么这是《货运崇拜编程》吗?似乎是这样-很多人必须采取这一步骤来保护自己的代码免受多个线程的侵害,而在我看来,实际上,在将事件用作多线程设计的一部分之前,事件需要比这多得多的关注。因此,那些没有特别注意的人也可能会忽略此建议-对于单线程程序来说这根本不是问题,并且实际上,由于volatile
大多数在线示例代码中都没有,因此该建议可能没有完全没有效果。
(并且delegate { }
在成员声明中分配空值是否更简单,这样您就不必首先检查null
?)
更新:如果不清楚,我确实掌握了建议的意图-避免在所有情况下都出现空引用异常。我的观点是,仅当另一个线程从该事件中退出时,才会发生此特定的null引用异常,而这样做的唯一原因是确保不会通过该事件接收到进一步的调用,而该技术显然无法实现。您可能会隐藏种族状况-最好公开一下!空异常有助于检测对组件的滥用。如果希望保护组件免受滥用,则可以遵循WPF的示例-将线程ID存储在构造函数中,如果另一个线程尝试直接与组件进行交互,则抛出异常。否则,实现一个真正的线程安全组件(不是一件容易的事)。
因此,我认为仅执行此复制/检查惯用语便是一种狂热的编程,这会给您的代码增加混乱和噪音。要真正保护自己免受其他线程的攻击,需要进行大量工作。
更新以回应Eric Lippert的博客文章:
因此,关于事件处理程序,我错过了一件主要的事情:“即使在事件未订阅后,事件处理程序也必须在被调用时保持健壮”,因此,显然,我们只需要关心事件的可能性代表被null
。对事件处理程序的要求是否记录在任何地方?
因此:“还有其他方法可以解决此问题;例如,初始化处理程序以使其具有从未删除的空动作。但是,执行空检查是标准模式。”
因此,我的问题剩下的一个片段是,为什么要显式-空检查“标准模式”?另一种方法是分配空的委托人,只需= delegate {}
要将其添加到事件声明中,这样就消除了在引发事件的每个位置上的一堆臭臭的仪式。确保空委托的实例化很便宜。还是我还缺少什么?
当然一定是(正如Jon Skeet所建议的那样),这仅仅是.NET 1.x的建议还没有像2005年那样被淘汰?
EventName(arguments)
无条件地调用事件的委托,而不是让它仅在非null时调用委托(如果为null,则不执行任何操作)。