委托不是语法糖,而是类型安全的函数指针;90%场景用Action/Func即可,仅当需显式命名签名、跨程序集暴露契约、复用强约束签名、满足第三方库要求或极少见序列化时才手动定义delegate。

委托不是语法糖,是类型安全的函数指针——直接用 Action、Func 就够了,90% 场景根本不用手写 delegate 关键字声明。
什么时候必须自己定义 delegate 类型
只有当需要显式命名委托签名、或跨程序集暴露回调契约时才值得手动定义。比如 Windows Forms 的事件参数封装、或 SDK 提供的异步完成回调接口。
- 需要在多个地方复用同一组参数+返回值签名,且希望编译期明确约束(比如
public delegate void DataProcessedHandler(string result, int count);) - 第三方库要求你传入特定命名的委托类型(如某些老版本 WCF 回调契约)
- 要序列化委托(极少见,通常意味着设计有问题)
其他情况一律优先用 Action<T>(无返回)、Func<T, TResult>(有返回)。
调用委托前必须检查 null,否则 NullReferenceException 无法避免
C# 不会自动为委托变量做空保护,哪怕它看起来“应该被赋值”。尤其在事件场景下,未订阅的事件字段就是 null。
- 安全调用写法:
handler?.Invoke(arg1, arg2);(C# 6+ 空条件调用) - 旧版写法:
if (handler != null) handler(arg1, arg2); - 事件调用更典型:
PropertyChanged?.Invoke(this, args);—— 忘了?.是引发生产环境崩溃的常见原因
委托实例捕获变量时,生命周期容易出错
闭包让委托能访问外部局部变量,但变量实际生命周期由委托对象决定,不是由代码块作用域决定。
- 错误示范:在循环中创建委托并引用循环变量
i,所有委托最终都指向最后一个值 - 正确做法:在循环内声明临时变量承接当前值,再捕获它:
int localI = i; list.Add(() => Console.WriteLine(localI)); - 异步中更危险:委托绑定的
this或局部对象可能已被 GC,导致ObjectDisposedException或静默失败
如果委托要长期存活(比如注册到全局事件总线),务必确认所捕获对象的生命周期是否可控。
委托和 Lambda 表达式不是等价替换,性能与调试体验不同
简单 Lambda(无闭包、无捕获)会被 JIT 编译为静态方法+委托缓存;一旦捕获变量,就会生成匿名类,带来分配开销。
- 高频调用路径(如游戏帧循环、网络包解析)避免在 Lambda 中捕获局部变量
- 调试时,Lambda 的堆栈显示为
DisplayClassX.<.ctor>b__Y</.ctor>,不如命名方法直观 - 反射获取委托目标方法名:
del.Method.Name对命名方法返回真实名,对 Lambda 返回编译器生成名
真正在意性能或可调试性时,宁可用私有方法 + 方法组转换:list.ForEach(ProcessItem); 而不是 list.ForEach(x => ProcessItem(x));。


















