Action和Func是C#最常用委托类型,非高级概念;Action用于无返回操作,Func最后一个泛型参数为返回值,需注意闭包陷阱与协变逆变规则。

直接说结论:Action 和 Func 不是“要学透才能用”的高级概念,而是 C# 里最常用的委托类型,日常写事件、异步回调、LINQ、配置注入时几乎天天见;真正卡住人的不是语法,是分不清「该用哪个」和「参数顺序怎么记」。
什么时候必须用 Action,而不是自己写委托?
Action 就是「只执行、不返回」的委托别名。你写 delegate void DoSomething(string s, int i),不如直接用 Action<string int></string> —— 编译器自动帮你做了映射,且所有 .NET 基础库(比如 List<t>.ForEach</t>、Task.Run)都认它。
- 常见场景:
button.Click += (s, e) => { ... }里的事件处理其实是Action<object eventargs></object>的变体;Task.Run(() => Console.WriteLine("done"))用的是Action - 参数个数有上限:最多支持 16 个泛型参数(
Action<t1></t1>),超了就得自定义委托 - 别手写
delegate:除非要重用同一签名多次且带名字(比如public delegate void LogHandler(string msg);),否则纯属多打字
Func 的返回值类型总在最后,这是硬规则
Func<t1 t2 tresult></t1> 的泛型参数列表中,最后一个一定是返回值类型,前面全是输入参数。这个顺序不能错,否则编译直接报 CS0411 错误。
- 错误写法:
Func<int string></int>想表示「输入 int,返回 string」——对;但写成Func<string int></string>就变成「输入 string,返回 int」,语义全反了 - 无参有返回:用
Func<t></t>,比如Func<string></string>表示「调用后返回 string」,常用于延迟计算:var lazyName = new Lazy<string>(() => GetNameFromDb());</string> - void 不能当返回类型:
Func<void></void>是非法的——这时候只能换Action
传参时捕获变量要注意生命周期
用 lambda 表达式创建 Action 或 Func 时,如果引用了外部局部变量(比如 for 循环里的 i),容易掉进闭包陷阱。
var actions = new List<Action>();
for (int i = 0; i < 3; i++) {
actions.Add(() => Console.WriteLine(i)); // 全部输出 3!
}
foreach (var a in actions) a();- 原因:lambda 捕获的是变量
i的引用,不是值;循环结束时i == 3,所有 action 都读这个最终值 - 修复方式:在循环内声明新变量,强制捕获当前值:
for (int i = 0; i Console.WriteLine(localI)); } - 注意:C# 5+ 的
foreach变量本身已按值捕获,但for仍需手动处理
最常被忽略的其实是协变和逆变——比如 Func<object></object> 能赋给 Func<string></string> 吗?不能;但 Func<string></string> 可以赋给 Func<object></object>(因为 string 是 object 的子类,返回值类型支持协变)。这种细节平时不显眼,一旦做泛型委托转换或接口设计就突然冒出来。


















