12

我刚刚在 MSDN 上阅读有关事件的页面,并且遇到了令我困惑的示例代码片段。

有问题的代码是这样的:

// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
EventHandler<CustomEventArgs> handler = RaiseCustomEvent;

我理解代码的意图,但我看不到那一行是如何复制任何东西的。它所做的只是复制参考;它实际上并没有制作委托实例的深层副本。所以为此,它实际上并没有阻止竞争条件。

我在这里遗漏了一些明显的东西吗?

4

5 回答 5

18

委托是不可变的,因此在该代码中获得的引用保证不会改变。如果用户在 null 检查后订阅或取消订阅,将创建一个新的委托并将其设置为事件。但是,由于您引用了一个完全不同的对象并调用它,您不必担心它为空。

于 2009-10-22T19:32:50.407 回答
5

你是对的; 它正在复制参考。

但是,代表是不可变的。当您向事件添加处理程序时,会创建一个新委托,将当前处理程序与新处理程序结合起来,然后分配给该字段。

该字段引用的 Delegate 实例不能更改,因此它确实避免了竞争条件。

于 2009-10-22T19:32:41.343 回答
3

Eric Lippert已经在一篇非常详细的文章中介绍了这一点。

于 2009-10-22T19:34:56.540 回答
1

这也是来自 MSDN..

“委托的调用列表是一组有序的委托,其中列表的每个元素恰好调用委托所代表的方法之一。调用列表可以包含重复的方法。在调用期间,方法按顺序调用它们出现在调用列表中。委托尝试调用其调用列表中的每个方法;重复项在每次出现在调用列表中时被调用一次。 委托是不可变的;一旦创建,委托的调用列表就不会改变。

于 2009-10-22T19:41:21.503 回答
0

if (whatever != null) whatever();看起来它确保在被调用whatever时永远不会为空whatever(),但它实际上并不能确保在线程场景中。whatever = null可以在检查和调用之间设置不同的线程。

Foo temp = whatever;
if (temp != null) temp();

此代码消除了 null 取消引用的可能性,因为temp它是本地的,因此永远不会被不同的线程修改。所以它确实防止了竞争条件。不过,它并不能阻止所有相关的竞争条件。Eric Lippert 对代码的其他一些问题进行了更详细的讨论。

于 2009-10-22T19:41:28.550 回答