我刚刚在MSDN上的events上阅读了一个页面,我遇到了一段令我困惑的示例代码。
有问题的代码是:
// 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;
我理解代码的意图,但我没有看到该特定行如何制作任何内容的副本。它所做的就是复制引用;它实际上并没有制作委托实例的深层副本。所以为此,它实际上并没有完全阻止竞争条件。
我错过了一些明显的东西吗?
答案 0 :(得分:18)
委托是不可变的,因此保证在该代码中获得的引用不会改变。如果用户在空检查后订阅或取消订阅,则将创建新的委托并将其设置为该事件。但是,由于您有一个完全不同的对象的引用并调用它,您不必担心它是null。
答案 1 :(得分:5)
你是对的;它正在复制参考。
然而,代表是不变的;当您向事件添加处理程序时,会创建一个新委托,将当前处理程序与新处理程序组合在一起,然后分配给该字段。
字段引用的Delegate实例无法更改,因此可以避免竞争条件。
答案 2 :(得分:3)
Eric Lippert已在非常详细的post中详细说明了这一点。
答案 3 :(得分:1)
这也来自MSDN ..
“委托的调用列表是一组有序的委托,其中列表的每个元素都只调用委托所代表的方法之一。调用列表可以包含重复的方法。在调用时,方法按它们在调用列表中出现的顺序调用。委托尝试调用其调用列表中的每个方法; 重复项每次出现在调用列表中时都会被调用一次。 代理是不可变的;一旦创建,代理的调用列表就不会改变。“
答案 4 :(得分:0)
if (whatever != null) whatever();
看起来确保whatever
在调用whatever()
时永远不会为空,但它实际上并不确保在线程方案中。另一个线程可以在支票和通话之间设置whatever = null
。
Foo temp = whatever;
if (temp != null) temp();
此代码消除了空取消引用的可能性,因为temp
是本地的,因此永远不会被其他线程修改。所以它确实可以防止竞争。但它并不能阻止所有相关的竞争条件。 Eric Lippert对代码中的其他一些问题进行了more elaborate讨论。