Microsoft's tutorial on events显示如何在触发event
之前检查null
:
protected virtual void OnChanged(EventArgs e)
{
if (Changed != null)
{ // Potential Race-condition at this point!
Changed(this, e);
}
}
但这会留下一个竞争条件,详见Eric Lippert's blog,他写道,事件应该通过本地事件触发以避免竞争条件:
protected virtual void OnChanged(EventArgs e)
{
ChangedEventHandler temp = Changed; // Local copy of the EventHandler
if (temp != null)
{ // Race condition avoided by local variable.
temp(this, e);
}
}
虽然这样可行,但它让许多错误的开发人员感到困惑,并且不会把它放在本地范围的事件中。
来自DailyCoding的另一个解决方案是始终将事件初始化为一个空处理程序,因此永远不需要进行空检查:
// Set with empty delegate, will never be null
public event ChangedEventHandler Changed = delegate { };
protected virtual void OnChanged(EventArgs e)
{
// Neither Null-check nor local variable is needed; just trigger the event.
Changed(this, e);
}
这个很有道理,很简单 但是,由于我看到这种技术很少在网上提及,我认为必须有一个原因 使用像这样的空委托初始化事件是否有缺点?
答案 0 :(得分:1)
Changed = null
,那么它将会中断。答案 1 :(得分:1)
你会看到一个绝对微小的性能损失,但在更高级的情况下会出现问题,例如序列化和反序列化类可能导致你丢失假事件处理程序,并且缺少空检查然后抛出异常。
答案 2 :(得分:0)
在你发布的Eric Lippert的博客文章中,他说:
还有其他方法可以解决这个问题;例如,初始化 具有永不删除的空操作的处理程序。但做一个 null check是标准模式。
但在此之前他还说:
删除呼叫站点周围的代码[空检查]不会减少代码[...]中的竞争条件数。 更糟糕的是,这样做会使竞争条件更难以被发现 缩小可以在不消除比赛的情况下进行比赛的窗口 它强>
这是因为正如他所描述的那样,它仍然可能发生
在委托值[到堆栈]的推送和调用它[...]的调用之间
所以基本上,如果你使用一个空的处理程序,你会遇到一些性能损失(这似乎是这里的共识)。所以你获得的是可读性,但最重要的是:奇怪的行为将更加明显。 (我从性能降低中推断出这一点 - >花费更长时间 - >更可能调用过时的处理程序) 所以,如果你完全意识到这些事情会发生,并且空检查不会打扰你,那就去吧。或者不要,如果你不想这样做。