委托链本质是多播委托,通过+=/-=操作符调用Delegate.Combine/Remove实现方法追加与移除;仅void返回值委托适合链式调用,非void仅返回最后一个结果;匿名函数因实例不同导致移除失败;异常会中断后续执行,需手动遍历并捕获。

委托链本质是多播委托
在 C# 中,用 += 给委托变量添加方法,不是简单地覆盖,而是把新方法追加到内部调用列表里——这个结构叫「多播委托」(MulticastDelegate)。只要委托类型签名一致,就能链式叠加。但要注意:void 返回值的委托才能安全组成链;如果委托返回非 void 类型,调用链时只会拿到最后一个方法的返回值,前面的全被丢弃。
+= 和 -= 实际调用 Delegate.Combine 和 Delegate.Remove
编译器把 += 编译成 Delegate.Combine,-= 编译成 Delegate.Remove。这意味着:
-
+=不会重复添加相同方法实例(基于引用相等判断) -
-=只移除第一个匹配的方法实例,不是全部 - 如果尝试移除一个从未添加过的方法,不会报错,只是静默忽略
- 委托链为空时,变量值为
null,直接调用会抛NullReferenceException
常见陷阱:匿名函数和闭包导致移除失败
下面这段代码看似能成功移除,实则无效:
Action action = () => Console.WriteLine("A");
action += () => Console.WriteLine("B");
action -= () => Console.WriteLine("B"); // ❌ 不起作用!原因:每次写 () => ... 都创建了新的委托实例,内存地址不同。Delegate.Remove 找不到原对象。正确做法是保留对要移除方法的引用:
Action b = () => Console.WriteLine("B");
action += b;
action -= b; // ✅ 成功调用委托链时异常会中断后续执行
委托链是顺序同步执行的。一旦某个方法抛出未捕获异常,后面的委托将完全跳过。没有内置的“继续执行下一个”的机制。若需容错,必须手动遍历 GetInvocationList() 并 try/catch 每一项:
foreach (Action a in action.GetInvocationList())
{
try { a(); }
catch { /* 忽略或记录 */ }
}这也意味着,别依赖委托链做关键路径的串行任务协调——它不是工作队列,也没有事务性。
委托链看起来简洁,但它的生命周期管理、异常传播、引用一致性都容易出隐性问题。真要构建事件系统或回调调度,优先考虑 event 关键字封装,或直接用 System.Collections.Generic.List 显式控制。







