C产品如何满足特定用户需求?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1048个文字,预计阅读时间需要5分钟。
选择 Action 还是 Func,只看一件事:
为什么 Func 合法而 Func 编译报错
Func 的设计强制要求有返回值——它的最后一个泛型参数就是返回类型,不能省,也不能是 void。所以 Func<void></void> 语法非法,编译器直接报 CS0453;而 Action 天然对应 void,所有重载都不带返回类型声明。
-
Func<int></int>✅ 表示「不接收参数,返回int」 -
Func<string bool></string>✅ 表示「接收string,返回bool」 -
Func<int void></int>❌ 编译失败:不是语法支持的泛型形式 -
Action<int></int>✅ 表示「接收int,无返回」
Predicate 不是新类型,只是 Func 的别名
Predicate<t></t> 和 Func<t bool></t> 在 IL 层和运行时完全等价,连内存布局都一样。它唯一的作用是语义提示:你传的这个函数,是用来做“是/否判断”的。
- 用在
List<t>.RemoveAll()</t>、Array.Exists()、Dictionary<tkey tvalue>.ContainsKey()</tkey>这类 API 里时,框架签名写的是Predicate<t></t>,但你传Func<t bool></t>也能过编译(因为类型兼容) - 自己写方法时,如果参数意图明确是“条件检查”,优先用
Predicate<t></t>,别人一眼能懂;但如果需要返回索引、对象或null,就必须换Func<t tresult></t> - 没有
Predicate<t1 t2></t1>—— 多参判断只能靠Func<t1 t2 bool></t1>
Action 和 Func 都支持最多 16 个参数,但别真写满
理论上 Action<t1></t1> 和 Func<t1></t1> 都存在,.NET Core 3.1+ 和 .NET 5+ 全支持。但实际中,超过 4 个参数就该警惕了:
- 可读性断崖式下降:
Func<string int bool datetime guid string></string>到底哪一个是返回值?前五个谁是谁?没人想数 - 闭包捕获变多时 GC 压力上升,尤其在循环里反复 new 这种多参委托
- 多数 LINQ 方法(如
Select、Where)只接受单参Func,强行塞多参得靠元组或匿名类包装,反而绕远路
协变与逆变让委托复用更灵活,但只对引用类型生效
Func<t person></t> 能接 Employee Find(string)(Employee 继承 Person),这是协变;Action<person></person> 能接 void Log(Employee e),这是逆变。但这些特性只适用于引用类型,值类型(如 int、DateTime)不参与变体转换。
- 把
Func<string employee></string>赋给Func<string person></string>✅ - 把
Action<employee></employee>赋给Action<person></person>✅ - 把
Func<int long></int>赋给Func<int int></int>❌ 值类型不支持协变 - 变体能力在 lambda 表达式中默认启用,无需额外标注
真正容易被忽略的是:泛型参数顺序和返回值位置是硬编码进委托定义里的,不是靠命名或注释约定。一旦写错位置(比如把返回类型放在 Func 中间),编译器不会帮你猜,只会报错——而且错误信息往往指向调用点,而不是定义点。
本文共计1048个文字,预计阅读时间需要5分钟。
选择 Action 还是 Func,只看一件事:
为什么 Func 合法而 Func 编译报错
Func 的设计强制要求有返回值——它的最后一个泛型参数就是返回类型,不能省,也不能是 void。所以 Func<void></void> 语法非法,编译器直接报 CS0453;而 Action 天然对应 void,所有重载都不带返回类型声明。
-
Func<int></int>✅ 表示「不接收参数,返回int」 -
Func<string bool></string>✅ 表示「接收string,返回bool」 -
Func<int void></int>❌ 编译失败:不是语法支持的泛型形式 -
Action<int></int>✅ 表示「接收int,无返回」
Predicate 不是新类型,只是 Func 的别名
Predicate<t></t> 和 Func<t bool></t> 在 IL 层和运行时完全等价,连内存布局都一样。它唯一的作用是语义提示:你传的这个函数,是用来做“是/否判断”的。
- 用在
List<t>.RemoveAll()</t>、Array.Exists()、Dictionary<tkey tvalue>.ContainsKey()</tkey>这类 API 里时,框架签名写的是Predicate<t></t>,但你传Func<t bool></t>也能过编译(因为类型兼容) - 自己写方法时,如果参数意图明确是“条件检查”,优先用
Predicate<t></t>,别人一眼能懂;但如果需要返回索引、对象或null,就必须换Func<t tresult></t> - 没有
Predicate<t1 t2></t1>—— 多参判断只能靠Func<t1 t2 bool></t1>
Action 和 Func 都支持最多 16 个参数,但别真写满
理论上 Action<t1></t1> 和 Func<t1></t1> 都存在,.NET Core 3.1+ 和 .NET 5+ 全支持。但实际中,超过 4 个参数就该警惕了:
- 可读性断崖式下降:
Func<string int bool datetime guid string></string>到底哪一个是返回值?前五个谁是谁?没人想数 - 闭包捕获变多时 GC 压力上升,尤其在循环里反复 new 这种多参委托
- 多数 LINQ 方法(如
Select、Where)只接受单参Func,强行塞多参得靠元组或匿名类包装,反而绕远路
协变与逆变让委托复用更灵活,但只对引用类型生效
Func<t person></t> 能接 Employee Find(string)(Employee 继承 Person),这是协变;Action<person></person> 能接 void Log(Employee e),这是逆变。但这些特性只适用于引用类型,值类型(如 int、DateTime)不参与变体转换。
- 把
Func<string employee></string>赋给Func<string person></string>✅ - 把
Action<employee></employee>赋给Action<person></person>✅ - 把
Func<int long></int>赋给Func<int int></int>❌ 值类型不支持协变 - 变体能力在 lambda 表达式中默认启用,无需额外标注
真正容易被忽略的是:泛型参数顺序和返回值位置是硬编码进委托定义里的,不是靠命名或注释约定。一旦写错位置(比如把返回类型放在 Func 中间),编译器不会帮你猜,只会报错——而且错误信息往往指向调用点,而不是定义点。

