自定义委托需显式声明返回值和参数类型,实例化有三种方式:方法组转换(推荐)、显式构造、lambda表达式;action和func是预定义泛型委托,分别用于无返回值和有返回值场景;调用前须判空或用?.invoke()避免nullreferenceexception;多播委托异常会中断后续执行且func只返回最后一个结果。

怎么声明和实例化一个自定义委托
自定义委托不是必须的,但当你需要明确语义、复用签名或跨模块传递行为时,它比泛型委托更清晰。关键点在于:委托声明必须显式写出返回类型和所有参数类型,且方法绑定时必须严格匹配——包括返回值类型(这点和方法重载规则不同)。
常见错误是把 void 委托误写成有返回值,或者参数顺序/类型稍有偏差导致编译失败,例如:
public delegate int Processor(string input); // 返回 int public static string DoWork(string s) => s.ToUpper(); // 返回 string → 不兼容
实例化方式有三种,优先级从高到低:
-
Processor p = DoWork;—— 方法组转换(推荐,简洁) -
Processor p = new Processor(DoWork);—— 显式构造(C# 1.0 风格,现在少用) -
Processor p = (s) => s.Length.ToString();—— Lambda 表达式(适合一次性逻辑)
Action 和 Func 是什么,什么时候该用它们
Action 和 Func 是 .NET 内置的泛型委托类型,本质就是预定义好的常用签名模板。它们不是语法糖,而是编译器直接识别的类型,所以不需额外声明就能用。
使用场景很明确:
- 用
Action<t></t>替代返回void、带 1 个参数的方法(如日志回调、状态更新) - 用
Func<t tresult></t>替代有返回值、带 1 个输入参数的方法(如转换器、校验器) - 参数超过 4 个时,
Func<t1></t1>仍可用,但可读性下降,此时建议改用自定义委托
注意:没有 Action(零参数)这种写法,要用 Action;而 Func 也不存在,因为 Func 至少要有一个 TResult 参数。
委托调用时 null 引用异常怎么避免
委托变量未赋值时默认为 null,直接调用会抛出 NullReferenceException。这不是“委托的问题”,而是你忘了检查空值——尤其在事件或回调参数场景下非常典型。
安全调用方式只有两种:
- 显式判空:
if (callback != null) callback("done"); - 用 C# 6+ 的空条件运算符:
callback?.Invoke("done");(注意不能省略.Invoke(),callback?()是非法语法)
别依赖“反正传进来就一定有”的假设。哪怕文档写着“必传”,运行时也可能因配置错误、异步竞态或 mock 测试被绕过。
多播委托(+=)为什么有时不按预期执行
委托支持合并(+=),形成多播委托链,调用时按注册顺序逐个执行。但有两个容易忽略的细节:
- 如果链中任一方法抛出异常,后续方法**不会继续执行**(除非你手动捕获)
-
Func<t></t>类型的多播委托只返回最后一个方法的结果,前面的返回值被丢弃
例如:
Action log = () => Console.Write("A");
log += () => { throw new Exception(); };
log += () => Console.Write("B"); // 这行永远不会运行
所以,不要把多播当成“可靠的通知广播机制”,它更适合轻量、无副作用、允许中断的场景,比如 UI 状态同步。真要保证全部执行,得自己封装循环调用逻辑。
委托的核心约束其实就一条:签名必须对齐。但实际踩坑往往不在声明,而在调用上下文——是否为空、是否被意外覆盖、是否在多线程中被并发修改。这些地方没加防护,再标准的 Func 也会崩。











