C#事件处理疑问:微软Collection<T>为何先复制事件委托?
为什么要先把
Changed事件赋值给临时变量再触发? 这个写法其实是C#里处理多播事件的经典安全范式,核心目的是避免多线程环境下的竞态条件导致的空引用异常,咱们一步步拆解它的优势:
1. 防止多线程下的NullReferenceException
假设现在有两个线程:
- 线程A:刚检查完
Changed != null,正准备调用Changed(...) - 线程B:在这一瞬间移除了最后一个订阅
Changed的事件处理程序,导致Changed变成null
如果直接用if(Changed != null) Changed(...),线程A接下来的调用就会抛出NullReferenceException——因为检查和调用不是原子操作,中间状态被修改了。
而先把Changed赋值给临时变量temp:
EventHandler<DinosaursChangedEventArgs> temp = Changed;
委托是引用类型,但赋值操作会复制当前的委托引用到temp里。这时候哪怕后续Changed被其他线程改成null,temp依然持有当时有效的委托引用(如果之前有订阅的话),后续调用temp?.Invoke(...)就不会出错。
2. 保证内存可见性(符合C#内存模型)
在多线程场景下,CPU的缓存优化可能导致某个线程读取到的Changed字段不是最新值。而将字段赋值给局部变量后,局部变量的访问会遵循更严格的内存可见性规则,确保我们拿到的是当前时刻Changed的真实状态,不会因为缓存过期导致错误判断。
补充:C# 6+的简化写法
如果你用的是C# 6或更高版本,其实可以用null条件运算符直接简化成:
Changed?.Invoke(this, new DinosaursChangedEventArgs(ChangeType.Added, newItem, index));
编译器会在后台自动帮你生成临时变量的逻辑,和手动写temp = Changed的效果完全一样,既简洁又安全。
反例:直接引用的风险
如果跳过临时变量直接写:
// 不安全!多线程下可能抛出NullReferenceException if (Changed != null) { Changed(this, new DinosaursChangedEventArgs(ChangeType.Added, newItem, index)); }
这种写法在单线程环境下没问题,但一旦放到多线程场景里,就可能出现前面说的竞态条件,导致程序崩溃。
内容的提问来源于stack exchange,提问作者J Doe
相关产品推荐
相关产品推荐

