委托未初始化调用会抛NullReferenceException;解绑失败因引用不等;事件触发须判空;多播委托异常会中断后续执行。

直接调用未初始化的委托变量会立刻抛出 NullReferenceException,这是最常踩、也最容易被忽略的坑。别指望编译器报错——它完全合法,但运行时必崩。
委托变量声明后必须绑定方法才能调用
委托是引用类型,声明但不赋值,默认值就是 null。此时调用等价于 null.Invoke(...),和对空对象调用任意实例方法一样,直接炸。
- 错误写法:
DataProcessDelegate processor;然后直接processor("data"); - 正确写法之一:
DataProcessDelegate processor = HandleData;(绑定已定义方法) - 正确写法之二:
DataProcessDelegate processor = data => Console.WriteLine(data);(绑定 lambda) - 安全兜底写法:
processor?.Invoke("data");—— 用空条件运算符避免崩溃,但不会执行任何逻辑
多播委托解绑失败:-= 不起作用的 3 个原因
用 += 绑定多个方法后,-= 看似能解绑,但经常“删不掉”。根本原因是:委托比较基于**引用相等性**,不是方法签名或内容。
- 匿名方法或 lambda 每次创建都是新实例,
-=传入的 lambda 和+=的不是同一个对象 → 解绑无效 - 实例方法绑定时用了不同对象实例(如
obj1.Method和obj2.Method),即使方法名相同也无法匹配 - 静态方法没问题,但混用实例+静态、或跨生命周期对象(如 Unity 中已 Destroy 的 MonoBehaviour)会导致引用失效
真正可靠的解绑方式是:保存原始委托实例,后续用同一引用解绑。var handler = new NotifyHandler(OnNotify); event += handler; ... event -= handler;
事件触发时忘记判空导致 NullReferenceException
事件本质是多播委托的封装,但它不自动做空检查。外部代码不能直接调用 event(...),但类内部 event?.Invoke(...) 必须显式加 ?,否则订阅者全为空时照样崩。
- 错误写法:
TemperatureChanged(this, EventArgs.Empty); - 基础修复:
TemperatureChanged?.Invoke(this, EventArgs.Empty); - 多线程安全加强:
var handler = Volatile.Read(ref TemperatureChanged); handler?.Invoke(this, EventArgs.Empty);,防止读取后被另一线程清空
委托链中一个方法抛异常,后续方法就不再执行
多播委托默认是“短路执行”:只要链上任意一个方法抛出未捕获异常,整个调用就中断,后面的方法永远不会被调用。这不是 bug,是设计行为。
- 验证方式:注册两个 handler,第一个 throw,第二个写
Console.WriteLine→ 第二个不会输出 - 想全部执行?必须手动遍历:
foreach (var d in multicast.GetInvocationList()) { try { d.DynamicInvoke("msg"); } catch (Exception ex) { Log.Error(ex); } } - 注意:
DynamicInvoke性能较差;若签名固定,可用强类型转换 +Invoke提升性能
委托本身不复杂,但它的“引用语义”和“运行时绑定”特性,让空引用、解绑失效、异常中断这些看似边缘的问题,在真实项目里高频出现。尤其在事件驱动、插件系统、回调链路中,稍不留意就会埋下静默失败或内存泄漏的隐患。










