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

为何微软文档中额外声明EventHandler?技术实现解析

问题

微软官方文档中关于events的示例代码如下:

class Counter
{
    public event EventHandler ThresholdReached;

    protected virtual void OnThresholdReached(EventArgs e)
    {
        EventHandler handler = ThresholdReached; // <-- (*)
        handler?.Invoke(this, e);
    }

    // 类的其他实现代码
}

有人疑问:标记(*)处的变量实例化设计动机是什么?为何不直接采用下面的简化写法?

class Counter
{
    public event EventHandler ThresholdReached;

    protected virtual void OnThresholdReached(EventArgs e)
    {
        ThresholdReached?.Invoke(this, e);
    }
}
回答

这本质是多线程环境下的线程安全问题,核心是避免竞态条件引发的NullReferenceException:

  • 先看历史背景:在C# 6引入?.空条件操作符之前,开发者必须手动把事件委托复制到临时变量。如果直接写if (ThresholdReached != null) ThresholdReached.Invoke(...),可能刚判断完事件不为null,另一个线程就取消了所有订阅,把事件置为null,这时执行Invoke就会抛出空引用异常。把委托复制到临时变量后,即便原事件被修改,我们调用的是临时变量里的委托引用,不会受影响。

  • 再看当前情况:C# 6及以后版本中,?.Invoke语法会被编译器自动转换成「先复制到临时变量,再判断调用」的逻辑,和手动写临时变量的效果完全一致。微软文档保留手动复制的写法,主要是为了明确展示线程安全的实现逻辑,让开发者清楚背后的原理,而不是只依赖语法糖。

简单说:两种写法在功能和线程安全性上没有区别,手动复制变量的写法是为了更直观地体现多线程场景下的安全处理思路,尤其适合给新手讲解事件的线程安全实现原理。

内容的提问来源于stack exchange,提问作者TMOTTM

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 11:45:28