You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:36:40