11

微软关于事件的教程展示了如何在触发之前检查它eventnull

protected virtual void OnChanged(EventArgs e) 
{
    if (Changed != null)
    {   // Potential Race-condition at this point!
        Changed(this, e);
    }
}

但这留下了一个竞争条件,正如Eric Lippert 的博客中所详述的那样,他在其中写道,应该通过本地事件触发事件以避免竞争条件:

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);
}

这个很有道理,也很简单。
但是,由于我在网上看到这种技术很少提及,我认为这一定是有原因的。
用这样的空委托初始化事件有缺点吗?

4

3 回答 3

1
  • 如果事件本来是空的,那么性能会受到轻微影响
  • 如果你在课堂上写过Changed = null,它会坏掉的。
于 2013-11-11T17:49:21.160 回答
1

你会看到一个非常小的性能损失,但是在更高级的情况下会出现问题,例如序列化和反序列化类可能会导致你丢失假事件处理程序,并且缺少空检查然后抛出异常。

于 2013-11-11T17:50:40.860 回答
0

在您发布的 Eric Lippert 的博客文章中,他说:

还有其他方法可以解决这个问题;例如,将处理程序初始化为具有永远不会删除的空操作。但是进行空检查是标准模式。

但在此之前,他还说:

删除调用站点周围的代码 [null 检查] 不会减少代码中竞争条件的数量 [...]。更糟糕的是,这样做会使竞争条件更难以通过缩小可能发生竞争的窗口而不消除它来检测。

这是因为正如他所描述的,它仍然可能发生

在将委托值推送到 [堆栈] 和调用它之间 [...]

所以基本上,如果你使用一个空的处理程序,你会遇到一些性能损失(这似乎是这里的共识)。所以你得到的是可读性,但最重要的是:奇怪的行为会更加明显。(我从较低的性能推断出这一点 -> 需要更长的时间 -> 更有可能调用过时的处理程序)因此,如果您完全意识到可能会发生这样的事情,并且 null 检查不会打扰您,那就去做吧。或者不要,如果你不想。

于 2013-11-11T21:33:27.327 回答