问:int Calculate(int a, int b, Func<int, int, int> operation) { return operation(a, b); } 以上代码里的func是什么作用?
Func<int, int, int> 在这里的作用是:定义了一个能接收两个 int 参数、并返回一个 int 结果的委托类型**,作为方法的“算法参数”。
简单理解:它就是一个可以让你“把计算方法塞进方法里”的参数。
Func<int, int, int> 中:
int:代表方法的返回值类型int, int:代表方法的两个参数类型所以这个委托可以指向任何符合以下签名的方法:
csharp
int 任意方法名(int x, int y)
生活类比:
这就像在淘宝买一个“智能炒菜机”,它有一个“烹饪模式”插槽(Func),这个插槽要求:
你不能把“只听音乐”的芯片插进去(参数不对),也不能把输出是“字符串”的芯片插进去(返回类型不对)。
Calculate 方法本身只负责执行,但不关心具体怎么计算。计算逻辑通过 operation 参数从外部传入。
使用示例:
csharp
// 传入加法
int sum = Calculate(5, 3, (a, b) => a + b); // 返回 8
// 传入减法
int diff = Calculate(5, 3, (a, b) => a - b); // 返回 2
// 传入乘法
int product = Calculate(5, 3, (a, b) => a * b); // 返回 15
如果没有 Func,你只能写三个方法:
csharp
int Add(int a, int b) => a + b;
int Subtract(int a, int b) => a - b;
int Multiply(int a, int b) => a * b;
调用时写死方法名,无法动态切换算法。
Func 是 .NET 内置的泛型委托,省去了你手动声明委托的麻烦。
不用 Func 的原始写法:
csharp
// 需要先声明一个委托类型
public delegate int MyMathOperation(int a, int b);
int Calculate(int a, int b, MyMathOperation operation)
{
return operation(a, b);
}
用 Func 的简化写法:
csharp
int Calculate(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}
所以 Func 的作用就是:让你不用自己写 delegate 声明,直接使用现成的泛型委托。
| Func 类型 | 参数个数 | 返回值 |
|---|---|---|
Func<int> | 0 个参数 | int |
Func<int, string> | 1 个 int 参数 | string |
Func<int, int, bool> | 2 个 int 参数 | bool |
Func<string, double, int, float> | 3 个参数(string, double, int) | float |
最多支持 16 个参数。
另一个常用委托 Action:与 Func 类似,但没有返回值。
csharp
Action<string> log = (msg) => Console.WriteLine(msg); // 无返回值
| 概念 | 生活类比 |
|---|---|
Func<int, int, int> | 一个“算法插槽”的规格说明书 |
(a, b) => a + b | 符合规格的“加法芯片” |
Calculate 方法 | 一个“计算器”,里面有个插槽可以插芯片 |
调用 Calculate 传入 (a,b)=>a+b | 把加法芯片插进计算器,机器执行加法 |
Func<int, int, int> 是一个委托类型,它定义了“能接收两个 int 并返回一个 int”的方法签名,让你可以把具体计算方法当作参数传给 Calculate 方法,从而实现算法的动态切换。
问:以上func功能,执行过程,是怎样的步骤?是否都在一个线程执行?
以这段代码为例:
csharp
int result = Calculate(5, 3, (a, b) => a + b);
步骤拆解:
| 步骤 | 做什么 | 代码层面 |
|---|---|---|
| 1 | 准备参数 | 计算 5 和 3 的值 |
| 2 | 创建委托实例 | 把 (a, b) => a + b 编译成一个匿名方法,并创建 Func<int,int,int> 委托指向它 |
| 3 | 调用 Calculate 方法 | 把 5、3、以及刚才创建的委托实例传入 |
| 4 | 进入 Calculate 方法体 | 执行 return operation(a, b); |
| 5 | 通过委托调用目标方法 | 调用 (a, b) => a + b,传入 a=5, b=3 |
| 6 | 执行加法逻辑 | 计算 5 + 3 = 8 |
| 7 | 返回值层层返回 | 加法返回 8 → Calculate 返回 8 → 赋值给 result |
关键点:步骤 5 中的 operation(a, b) 看起来像普通方法调用,但实际上是通过委托实例间接调用目标方法。
是的,完全在同一个线程中执行,不会自动创建新线程。
text主线程(假设是 UI 线程或 Main 线程): ├─ 步骤1-2:准备参数和委托(主线程) ├─ 步骤3:调用 Calculate(主线程) ├─ 步骤4:进入 Calculate 方法体(主线程) ├─ 步骤5:通过委托调用 Lambda(主线程) ├─ 步骤6:执行加法(主线程) └─ 步骤7:返回结果(主线程)
整个过程是同步、阻塞的——Calculate 方法不会立即返回,而是要等 operation(a, b) 执行完才会返回。
同一线程执行就像你亲自做三明治:
你拿出面包(准备参数)
你拿出火腿(创建委托)
你把火腿夹进面包(调用方法)
你咬一口(执行委托内的逻辑)
你咽下去(返回值)
全部是你自己(同一个线程)完成的,没有叫别人帮忙。
如果开启新线程(比如用 Task.Run),就像你叫外卖:
你下单后可以继续做其他事(主线程不阻塞)
外卖员在另一个线程里做三明治
做好后通知你
但上面 Func 的例子没有这种操作,所以不涉及多线程。
Func 本身不会跨线程,但可以把 Func 传给会开新线程的方法:
csharp
// 这会开新线程执行委托
Task.Run(() => Calculate(5, 3, (a,b) => a + b));
或者:
csharp
// 委托内部自己开线程
Func<int,int,int> func = (a,b) => {
return Task.Run(() => a + b).Result; // 危险写法,仅为示例
};
但在常规用法中(如 LINQ、List.Sort、上面的 Calculate),委托和调用它的方法永远在同一个线程。
| 问题 | 答案 |
|---|---|
| 执行过程是怎样的? | 普通方法调用流程:准备参数 → 调用 Calculate → 内部通过委托调用 Lambda → 返回 |
| 是否在一个线程执行? | 是,完全在同一个线程,同步执行 |
Func 会自己开线程吗? | 不会,它只是委托,不涉及任何线程操作 |
| 委托调用比普通调用慢吗? | 有极微小的性能开销(委托调用需要一次间接寻址),但通常可以忽略 |
一句话记住:Func 委托就是一个可在代码中传递的方法引用,它的执行过程和普通方法调用基本一样,同步、不跨线程。
问:Func 委托执行过程和普通方法调用基本一样,那Func 委托的存在意义是什么?
这是一个很深刻的问题。既然执行过程几乎一样,那 Func 委托存在的意义是什么?
核心答案是:它把“调用哪个方法”这个决定权,从编译时推迟到了运行时。
换句话说,普通方法调用是“写死”的,而委托是“可插拔”的。
csharp
int Add(int a, int b) => a + b;
int result = Add(5, 3); // 编译时就确定调用的是 Add
特点:你在写代码的那一刻,就已经决定了要调用 Add。如果想改成减法,必须修改代码、重新编译。
csharp
int Calculate(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}
// 运行时决定调用什么
int result1 = Calculate(5, 3, (a,b) => a + b); // 加法
int result2 = Calculate(5, 3, (a,b) => a - b); // 减法
int result3 = Calculate(5, 3, (a,b) => a * b); // 乘法
特点:Calculate 方法本身完全不关心具体是什么算法,调用方可以在运行时自由选择。
你去一家只卖咖啡的店。你每次去,不用说话,店员直接给你一杯美式。
优点:简单、快
缺点:想喝果汁?去另一家店(改代码)
一台机器有一个“制作饮料”的插槽(Func),你可以插入不同的配方芯片:
机器的代码(Calculate)不用改,换芯片(传不同的委托)就能改变行为。
| 问题 | 普通方法 | Func 委托 |
|---|---|---|
| 算法可替换吗 | 否,写死了 | 是,调用时决定 |
| 方法能当参数传吗 | 不能 | 能 |
| 能延迟执行吗 | 不能(立即执行) | 能(存起来以后用) |
场景1:LINQ 的 Where 筛选
csharp
// 筛选条件不是写死的,调用者决定
var result = list.Where(x => x.Age > 18); // 条件1
var result = list.Where(x => x.Name.StartsWith("张")); // 条件2
如果 Where 不用委托,你就要写 WhereAgeGreaterThan18()、WhereNameStartsWithZhang() 等无数个方法。
场景2:排序规则
csharp
// 今天按年龄排,明天按姓名排
list.Sort((a,b) => a.Age.CompareTo(b.Age));
list.Sort((a,b) => a.Name.CompareTo(b.Name));
场景3:回调
csharp// 下载完成后做什么,由调用方决定
DownloadFile(url, () => Console.WriteLine("下载完成"));
DownloadFile(url, () => SendEmail());
有人可能会问:“那用接口也能实现同样的效果,为什么用委托?”
| 对比 | 接口 | 委托 |
|---|---|---|
| 需要定义新类型吗 | 需要(定义一个 ICalculator 接口) | 不需要(直接用 Func) |
| 代码量 | 较多 | 较少 |
| 灵活性 | 一个接口可以有多个方法 | 委托只关注单个方法签名 |
| 适用场景 | 多个相关操作的组合 | 单个操作/算法的注入 |
简单原则:只需要传一个方法 → 用委托;需要传一组相关方法 → 用接口。
| 层面 | 说明 |
|---|---|
| 语法上 | 把方法变成一种可以传递、赋值、存储的对象 |
| 设计上 | 实现策略模式,把算法和调用方解耦 |
| 运行时 | 让调用方决定具体执行什么逻辑,而不是写死在方法内部 |
一句话记住:
普通方法调用是“我知道我要做什么,我直接做”,
委托是“我不知道你要做什么,你来告诉我,我帮你做”。
这就是 Func 委托存在的意义——不是执行方式不同,而是代码的组织方式和灵活度完全不同。
本文作者:ZeroEngineer
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!