多播委托是+=的默认行为,非高级功能;需用?.invoke()安全调用,移除时须引用一致,异常会中断后续执行且仅返回最后一个值。

多播委托不是“高级功能”,而是 += 的默认行为
只要用 += 给同一个委托变量反复添加方法,C# 就自动构建多播委托链——它不是要额外启用的模式,而是你日常写事件订阅时已经在用的东西。底层调用的是 Delegate.Combine,但你不该手动调;编译器和运行时已为你封装好空值安全、类型校验和链表维护。
常见错误现象:handler = MethodA; handler = MethodB; —— 这根本没形成多播,第二次赋值直接覆盖了第一次,最终只执行 MethodB。
-
+=才是追加;=是替换 - 左边为
null时,+=会自动初始化,无需提前判空 - 别用
Delegate.Combine(a, b)手动拼接:易漏 null 判断、可读性差、不支持泛型推导
调用前必须用 ?.Invoke(),否则必崩
哪怕你刚加完两个方法,也不能保证委托非空:所有订阅者可能已被 -= 移除,或初始就是 null。直接调用 handler() 会抛 NullReferenceException,这不是偶发 bug,而是确定性崩溃。
- 推荐写法:
handler?.Invoke("data");—— 简洁、线程安全、C# 6.0+ 原生支持 - 若委托有返回值(如
Func<int></int>),?.Invoke()会返回null或默认值(如0),需按业务判断是否可接受 - 不要包一层
try/catch来“兜底”空引用:这是设计缺陷,不是流程分支
移除失败?大概率是闭包或匿名方法引用不一致
-= 不是按方法名或签名匹配,而是严格比对委托对象的引用地址。哪怕逻辑一模一样,两次写的 lambda 表达式也会生成两个不同实例,导致移除失败、内存泄漏或重复执行。
- 命名方法可安全移除:
handler += MethodA; handler -= MethodA; - lambda 必须复用:先声明再引用,
var action = () => Console.WriteLine("x"); handler += action; handler -= action; - 带捕获变量的 lambda 更危险:哪怕只改一个局部变量值,新构造的委托就 ≠ 原始引用
- 移除后结果可能为
null,务必检查:var newDel = handler - oldDel; if (newDel != null) newDel();
异常中断 + 返回值丢弃,这两点最容易被当“理所当然”
多播委托调用时,一旦某个方法抛出未捕获异常,后续所有方法**立刻停止执行**;而所有方法的返回值中,只有最后一个的会被返回,前面全丢弃。这不是 bug,是设计事实——但它和多数人直觉相悖。
- 想“全部执行不管异常”?必须手动遍历:
foreach (var d in handler.GetInvocationList()) { try { d.DynamicInvoke(arg); } catch { /* 忽略或记录 */ } } - 想聚合多个返回值?不能依赖原生调用,得自己循环收集结果
- 事件通知类场景(如日志+UI更新+告警)强烈建议显式遍历,而非直接
handler?.Invoke()
真正容易被忽略的,是“多播委托的链式行为在异常和返回值上完全不可组合”——它看起来像集合,实则没有集合语义。把 += 当成订阅、把 ?.Invoke() 当成触发、把异常/返回值逻辑收归到显式循环里,才不容易翻车。










