C# 中内置委托 Action 和 Func 的定义、使用和注意事项
一、先理解:什么是委托
在 C# 中,委托可以理解为:
一种可以保存方法的变量类型。
平时变量可以保存数字、字符串、对象:
int age = 18;
string name = "小明";
委托则可以保存“方法”:
Action sayHello = () =>
{
Console.WriteLine("你好");
};
sayHello();
这里的 sayHello 不是普通数据,而是保存了一段可以执行的代码。
可以这样理解:
普通变量保存数据,委托变量保存方法。
二、为什么需要内置委托
在没有 Action 和 Func 之前,如果我们想定义一个委托,通常要自己写:
public delegate void MyDelegate();
然后再使用:
MyDelegate method = () =>
{
Console.WriteLine("执行方法");
};
method();
但是很多委托的形状都很常见,比如:
- 没有参数,没有返回值
- 有一个参数,没有返回值
- 有两个参数,没有返回值
- 没有参数,有返回值
- 有一个参数,有返回值
- 有多个参数,有返回值
如果每次都自己定义委托,就会很麻烦。
所以 C# 提供了常用的内置委托:
ActionFunc
它们可以覆盖大多数常见场景。
一句话:
Action和Func是 C# 已经帮我们定义好的通用委托类型。
三、Action 是什么
Action 表示:
没有返回值的方法。
也就是说,只要一个方法的返回类型是 void,就有机会用 Action 表示。
1. 没有参数、没有返回值
Action sayHello = () =>
{
Console.WriteLine("你好,C#");
};
sayHello();
这里:
Action表示一个没有参数、没有返回值的方法。sayHello是委托变量。() => { ... }是匿名函数。sayHello()表示调用这个委托。
也可以写成普通方法:
static void SayHello()
{
Console.WriteLine("你好,C#");
}
Action action = SayHello;
action();
2. 一个参数、没有返回值
如果方法有一个参数,可以使用 Action<T>。
Action<string> printName = name =>
{
Console.WriteLine("姓名:" + name);
};
printName("小明");
这里:
Action<string>
表示:
这个委托保存的方法需要一个
string参数,并且没有返回值。
等价于自己定义:
public delegate void MyAction(string value);
3. 多个参数、没有返回值
如果方法有多个参数,可以继续写多个泛型类型。
Action<string, int> printInfo = (name, age) =>
{
Console.WriteLine($"{name} 今年 {age} 岁");
};
printInfo("小明", 18);
这里:
Action<string, int>
表示:
这个委托保存的方法有两个参数,第一个是
string,第二个是int,没有返回值。
再比如:
Action<string, int, double> printScore = (name, age, score) =>
{
Console.WriteLine($"{name},年龄 {age},成绩 {score}");
};
printScore("小红", 17, 95.5);
四、Func 是什么
Func 表示:
有返回值的方法。
它和 Action 最大的区别是:
Action没有返回值。Func一定有返回值。
1. 没有参数、有返回值
Func<int> getNumber = () =>
{
return 100;
};
int result = getNumber();
Console.WriteLine(result); // 输出 100
这里:
Func<int>
表示:
没有参数,返回一个
int。
如果函数体只有一个表达式,可以简写:
Func<int> getNumber = () => 100;
2. 一个参数、有返回值
Func<int, int> square = x =>
{
return x * x;
};
Console.WriteLine(square(5)); // 输出 25
这里:
Func<int, int>
表示:
第一个
int是参数类型,第二个int是返回值类型。
也就是:
Func<参数类型, 返回值类型>
可以简写:
Func<int, int> square = x => x * x;
3. 多个参数、有返回值
Func<int, int, int> add = (a, b) =>
{
return a + b;
};
Console.WriteLine(add(3, 5)); // 输出 8
这里:
Func<int, int, int>
前两个 int 是参数类型,最后一个 int 是返回值类型。
也就是:
Func<参数1类型, 参数2类型, 返回值类型>
再看一个例子:
Func<string, int, string> buildMessage = (name, age) =>
{
return $"{name} 今年 {age} 岁";
};
Console.WriteLine(buildMessage("小明", 18));
这里:
Func<string, int, string>
表示:
参数是
string和int,返回值是string。
五、Action 和 Func 的核心区别
| 对比项 | Action | Func |
|---|---|---|
| 是否有返回值 | 没有返回值 | 必须有返回值 |
| 返回类型 | void |
最后一个泛型参数 |
| 常见形式 | Action<T> |
Func<T, TResult> |
| 适合场景 | 执行动作 | 计算结果 |
简单记忆:
只做事,不返回结果,用
Action。
做完事,还要返回结果,用Func。
例子:
Action<string> print = text => Console.WriteLine(text);
这个只是打印,不返回结果,所以用 Action。
Func<int, int, int> add = (a, b) => a + b;
这个要返回计算结果,所以用 Func。
六、Action 的常见写法
1. 保存普通方法
static void PrintHello()
{
Console.WriteLine("Hello");
}
Action action = PrintHello;
action();
2. 保存匿名函数
Action action = delegate
{
Console.WriteLine("Hello");
};
action();
这是较早的匿名方法写法。
3. 保存 Lambda 表达式
Action action = () =>
{
Console.WriteLine("Hello");
};
action();
如果只有一行代码,可以简写:
Action action = () => Console.WriteLine("Hello");
4. 带参数的 Action
Action<string> print = text => Console.WriteLine(text);
print("你好");
多个参数:
Action<string, int> printUser = (name, age) =>
{
Console.WriteLine($"{name}:{age}");
};
printUser("小明", 18);
七、Func 的常见写法
1. 保存普通方法
static int Add(int a, int b)
{
return a + b;
}
Func<int, int, int> add = Add;
Console.WriteLine(add(10, 20));
2. 保存 Lambda 表达式
Func<int, int, int> add = (a, b) =>
{
return a + b;
};
Console.WriteLine(add(10, 20));
简写:
Func<int, int, int> add = (a, b) => a + b;
3. 返回字符串
Func<string, string> greet = name => "你好," + name;
Console.WriteLine(greet("小明"));
4. 返回布尔值
Func<int, bool> isAdult = age => age >= 18;
Console.WriteLine(isAdult(20)); // True
Console.WriteLine(isAdult(16)); // False
这种返回 bool 的 Func 很常见,常用于判断条件。
八、Action 和 Func 的调用方式
委托变量可以像方法一样调用。
Action sayHello = () => Console.WriteLine("Hello");
sayHello();
也可以使用 Invoke:
sayHello.Invoke();
带参数:
Action<string> print = text => Console.WriteLine(text);
print("你好");
print.Invoke("你好");
有返回值:
Func<int, int, int> add = (a, b) => a + b;
int result1 = add(1, 2);
int result2 = add.Invoke(1, 2);
常见写法更推荐直接像方法一样调用:
add(1, 2);
九、使用场景一:作为方法参数
Action 和 Func 最常见的用途之一,就是把一段逻辑传给另一个方法。
1. 把 Action 当作参数
static void DoWork(Action callback)
{
Console.WriteLine("开始工作");
callback();
Console.WriteLine("工作结束");
}
调用:
DoWork(() =>
{
Console.WriteLine("执行传进来的操作");
});
输出:
开始工作
执行传进来的操作
工作结束
这里的 callback 可以理解为“回调函数”:
我先把一段代码交给你,你在合适的时候帮我执行。
2. 把 Func 当作参数
static int Calculate(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}
调用:
int sum = Calculate(10, 5, (x, y) => x + y);
int difference = Calculate(10, 5, (x, y) => x - y);
int product = Calculate(10, 5, (x, y) => x * y);
Console.WriteLine(sum); // 15
Console.WriteLine(difference); // 5
Console.WriteLine(product); // 50
这样 Calculate 方法就不需要固定写死加法、减法或乘法,而是由外部传入计算规则。
十、使用场景二:简化重复代码
假设有三段代码都需要记录开始和结束:
Console.WriteLine("开始");
// 做事情 A
Console.WriteLine("结束");
Console.WriteLine("开始");
// 做事情 B
Console.WriteLine("结束");
可以用 Action 抽取公共流程:
static void RunWithLog(Action action)
{
Console.WriteLine("开始");
action();
Console.WriteLine("结束");
}
使用:
RunWithLog(() =>
{
Console.WriteLine("保存用户");
});
RunWithLog(() =>
{
Console.WriteLine("发送邮件");
});
如果任务有返回值,可以用 Func:
static T RunWithLog<T>(Func<T> func)
{
Console.WriteLine("开始");
T result = func();
Console.WriteLine("结束");
return result;
}
使用:
int result = RunWithLog(() =>
{
return 100 + 200;
});
Console.WriteLine(result);
十一、使用场景三:LINQ 中的 Func
LINQ 中大量使用 Func。
例如:
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var evenNumbers = numbers.Where(n => n % 2 == 0);
这里的:
n => n % 2 == 0
本质上可以理解为一个:
Func<int, bool>
也就是:
输入一个
int,返回一个bool,用来判断这个数字是否满足条件。
再比如:
var squares = numbers.Select(n => n * n);
这里的:
n => n * n
可以理解为:
Func<int, int>
表示:
输入一个
int,返回一个新的int。
对象列表示例:
class Student
{
public string Name { get; set; }
public int Score { get; set; }
}
List<Student> students = new List<Student>
{
new Student { Name = "小明", Score = 95 },
new Student { Name = "小红", Score = 80 },
new Student { Name = "小刚", Score = 59 }
};
var passedStudents = students.Where(s => s.Score >= 60);
var names = students.Select(s => s.Name);
这里:
s => s.Score >= 60是判断条件。s => s.Name是转换规则。
十二、使用场景四:事件和回调
简单事件可以使用 Action。
class Button
{
public event Action Click;
public void Press()
{
Console.WriteLine("按钮被按下");
Click?.Invoke();
}
}
使用:
Button button = new Button();
button.Click += () =>
{
Console.WriteLine("处理点击事件");
};
button.Press();
带参数事件:
class Downloader
{
public event Action<int> ProgressChanged;
public void Download()
{
for (int progress = 0; progress <= 100; progress += 25)
{
ProgressChanged?.Invoke(progress);
}
}
}
使用:
Downloader downloader = new Downloader();
downloader.ProgressChanged += progress =>
{
Console.WriteLine($"当前进度:{progress}%");
};
downloader.Download();
注意:
公开给外部使用的标准事件,实际项目中通常更推荐
EventHandler或EventHandler<TEventArgs>。
Action更适合简单、内部、教学或轻量场景。
十三、使用场景五:策略传入
有时候,我们希望一个方法的行为可以由外部决定。
例如筛选数字:
static List<int> Filter(List<int> numbers, Func<int, bool> condition)
{
List<int> result = new List<int>();
foreach (int number in numbers)
{
if (condition(number))
{
result.Add(number);
}
}
return result;
}
使用:
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> evenNumbers = Filter(numbers, n => n % 2 == 0);
List<int> bigNumbers = Filter(numbers, n => n > 3);
这里的 condition 就是外部传进来的判断策略。
如果不用 Func,我们可能要为每一种筛选规则都写一个单独的方法。
使用 Func 后,一个 Filter 方法就能支持很多规则。
十四、使用场景六:延迟执行
Func 可以把“现在不执行,等需要的时候再执行”的逻辑保存下来。
Func<int> getRandomNumber = () =>
{
Console.WriteLine("正在生成随机数");
return new Random().Next(1, 100);
};
这段代码只是把逻辑保存到 getRandomNumber 中,还没有真正生成随机数。
只有调用时才会执行:
int number = getRandomNumber();
这叫延迟执行。
简单理解:
Func保存的是一张“以后再执行”的任务卡。
十五、使用场景七:异步方法
异步方法也可以用 Func 和 Action 表示,但要注意返回类型。
1. 异步无返回结果:Func
Func<Task> loadDataAsync = async () =>
{
await Task.Delay(1000);
Console.WriteLine("数据加载完成");
};
await loadDataAsync();
虽然这个异步方法没有返回业务数据,但它返回的是 Task,所以应该用:
Func<Task>
而不是:
Action
2. 异步有返回结果:Func<Task>
Func<Task<int>> getNumberAsync = async () =>
{
await Task.Delay(1000);
return 100;
};
int number = await getNumberAsync();
带参数:
Func<string, Task<string>> downloadAsync = async url =>
{
await Task.Delay(1000);
return "下载完成:" + url;
};
string result = await downloadAsync("https://example.com");
注意:
异步逻辑通常用
Func<Task>或Func<Task<T>>,不要随便用async void或Action。
十六、Action 和 Func 的泛型参数顺序
这是初学者最容易混淆的地方。
Action 的泛型参数都是参数类型
Action<string, int, double>
表示:
void 方法名(string value1, int value2, double value3)
它没有返回值。
Func 的最后一个泛型参数是返回值类型
Func<string, int, double>
表示:
double 方法名(string value1, int value2)
注意:
- 前面的
string和int是参数类型。 - 最后的
double是返回值类型。
再看几个例子:
Func<int>
表示:
int 方法名()
Func<int, string>
表示:
string 方法名(int value)
Func<int, int, string>
表示:
string 方法名(int value1, int value2)
记忆口诀:
Action全是参数。
Func最后一个是返回值。
十七、Action 和 Func 可以有多少个参数
在 .NET 中,常见的 Action 和 Func 泛型委托支持多个参数。
常见形式:
Action
Action<T1>
Action<T1, T2>
Action<T1, T2, T3>
Func<TResult>
Func<T1, TResult>
Func<T1, T2, TResult>
Func<T1, T2, T3, TResult>
实际框架中通常最多支持到 16 个输入参数。
不过教学和实际开发中要提醒学生:
如果一个委托需要很多参数,往往说明设计可以优化。
比如下面这种写法可读性很差:
Action<string, int, double, bool, DateTime> action;
更推荐把相关参数封装成一个类:
class UserInfo
{
public string Name { get; set; }
public int Age { get; set; }
public double Score { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedTime { get; set; }
}
然后:
Action<UserInfo> action;
这样代码更容易理解和维护。
十八、注意事项一:返回值类型必须匹配
Func<int, int> square = x => x * x;
这里要求返回 int。
下面这样是错误的:
// 错误:要求返回 int,但实际返回 string
Func<int, int> wrong = x => "结果是:" + x;
应该改成:
Func<int, string> right = x => "结果是:" + x;
同理,参数类型也必须匹配:
Action<int> print = number => Console.WriteLine(number);
print(100);
不能传字符串:
// 错误
print("100");
十九、注意事项二:Action 不能返回值
错误写法:
// 错误:Action 没有返回值
Action<int> square = x => x * x;
因为:
x => x * x
会产生一个结果。
如果要返回结果,应该使用 Func:
Func<int, int> square = x => x * x;
如果真的只是执行动作,不需要返回值,可以写:
Action<int> printSquare = x =>
{
Console.WriteLine(x * x);
};
二十、注意事项三:Func 必须返回值
错误写法:
// 错误:Func<int> 要求返回 int
Func<int> getNumber = () =>
{
Console.WriteLine("没有返回数字");
};
正确写法:
Func<int> getNumber = () =>
{
Console.WriteLine("返回数字");
return 100;
};
如果不需要返回值,就使用 Action:
Action print = () =>
{
Console.WriteLine("不需要返回值");
};
二十一、注意事项四:语句 Lambda 要写 return
表达式 Lambda 可以自动返回结果:
Func<int, int> square = x => x * x;
语句 Lambda 使用 {} 后,需要显式写 return:
Func<int, int> square = x =>
{
return x * x;
};
错误写法:
// 错误
Func<int, int> square = x =>
{
x * x;
};
只要写了 {},就不是简单表达式了,返回值要用 return 明确写出来。
二十二、注意事项五:调用前注意是否为 null
委托变量可能没有赋值。
Action action = null;
action(); // 会报 NullReferenceException
更安全的写法:
action?.Invoke();
带参数:
Action<string> print = null;
print?.Invoke("你好");
有返回值时要注意默认值:
Func<int> getNumber = null;
int result = getNumber?.Invoke() ?? 0;
这里:
- 如果
getNumber不为null,就调用它。 - 如果
getNumber是null,就使用默认值0。
二十三、注意事项六:闭包问题
Action 和 Func 经常配合 Lambda 使用,而 Lambda 可以访问外部变量。
这种现象叫闭包。
int count = 0;
Action increase = () =>
{
count++;
Console.WriteLine(count);
};
increase(); // 1
increase(); // 2
increase(); // 3
这很方便,但也可能带来意外。
int number = 10;
Func<int> getNumber = () => number;
number = 20;
Console.WriteLine(getNumber()); // 输出 20
很多初学者以为会输出 10,但实际输出 20。
原因是:
Lambda 捕获的是变量本身,不只是当时的值。
如果想固定当时的值,可以复制一份:
int number = 10;
int snapshot = number;
Func<int> getNumber = () => snapshot;
number = 20;
Console.WriteLine(getNumber()); // 输出 10
二十四、注意事项七:循环中的闭包
看下面的例子:
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (Action action in actions)
{
action();
}
很多初学者会以为输出:
0
1
2
但在循环变量捕获的场景中,实际结果可能不是自己预期的。
更稳妥的写法是在循环内部复制一份:
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
int current = i;
actions.Add(() => Console.WriteLine(current));
}
foreach (Action action in actions)
{
action();
}
这样每个 Lambda 捕获的是各自的 current。
教学时可以这样说:
循环变量像同一个盒子反复换数字,复制一份就是给每次循环准备一个新盒子。
二十五、注意事项八:不要让委托过于复杂
下面这种代码虽然能写,但不适合教学和维护:
Func<Student, bool> condition = s =>
s.Score > 60
&& s.Name.StartsWith("小")
&& s.Name.Length > 1
&& s.Score + s.ExtraScore > 90;
如果逻辑变复杂,建议提取成普通方法:
static bool IsExcellentStudent(Student student)
{
return student.Score > 60
&& student.Name.StartsWith("小")
&& student.Name.Length > 1
&& student.Score + student.ExtraScore > 90;
}
然后:
Func<Student, bool> condition = IsExcellentStudent;
原则:
短逻辑可以用 Lambda,复杂逻辑应该提取成有名字的方法。
二十六、注意事项九:异步时不要用 Action 表示 async 逻辑
不推荐:
Action action = async () =>
{
await Task.Delay(1000);
Console.WriteLine("完成");
};
这类写法容易变成类似 async void 的效果,异常处理和等待流程都不够清晰。
更推荐:
Func<Task> actionAsync = async () =>
{
await Task.Delay(1000);
Console.WriteLine("完成");
};
await actionAsync();
如果有返回值:
Func<Task<int>> getNumberAsync = async () =>
{
await Task.Delay(1000);
return 100;
};
int number = await getNumberAsync();
记住:
异步无结果用
Func<Task>,异步有结果用Func<Task<T>>。
二十七、注意事项十:多播委托的返回值问题
委托可以同时保存多个方法,这叫多播委托。
Action 比较容易理解:
Action action = () => Console.WriteLine("第一个");
action += () => Console.WriteLine("第二个");
action();
输出:
第一个
第二个
但是 Func 有返回值,如果多个方法都返回结果,会发生什么?
Func<int> func = () =>
{
Console.WriteLine("第一个");
return 1;
};
func += () =>
{
Console.WriteLine("第二个");
return 2;
};
int result = func();
Console.WriteLine(result);
输出类似:
第一个
第二个
2
多个方法都会执行,但最终拿到的通常是最后一个方法的返回值。
所以:
多播委托更适合
Action。
对于Func,如果关心每个返回值,不建议直接多播。
二十八、Action 和 Func 与自定义委托的选择
既然有了 Action 和 Func,是不是就不需要自定义委托了?
不是。
Action 和 Func 适合通用场景。
例如:
Func<int, int, int> operation
它能表达“两个整数输入,一个整数输出”,但看不出业务含义。
有时自定义委托更清楚:
public delegate decimal DiscountCalculator(decimal price);
这个名字直接告诉我们:
这是一个折扣计算器。
对比:
| 写法 | 优点 | 缺点 |
|---|---|---|
Action / Func |
简洁、通用、少写代码 | 业务含义不够明显 |
| 自定义委托 | 名字能表达业务意义 | 多写一段定义 |
建议:
- 简单逻辑、局部使用,用
Action/Func。 - 公开 API、复杂业务、需要表达明确含义时,可以自定义委托。
二十九、Action 和 Func 与 Predicate 的关系
C# 里还有一个常见委托:
Predicate<T>
它表示:
bool 方法名(T value)
例如:
Predicate<int> isEven = n => n % 2 == 0;
它和下面写法很像:
Func<int, bool> isEven = n => n % 2 == 0;
都表示输入一个 int,返回一个 bool。
区别是:
Predicate<T>更强调“判断条件”。Func<T, bool>更通用。
初学时重点掌握 Action 和 Func 即可。
三十、完整示例:使用 Action 和 Func 制作小型计算器
using System;
class Program
{
static void Main()
{
Action<string> printTitle = title =>
{
Console.WriteLine("==== " + title + " ====");
};
Func<int, int, int> add = (a, b) => a + b;
Func<int, int, int> subtract = (a, b) => a - b;
Func<int, int, int> multiply = (a, b) => a * b;
printTitle("计算器");
PrintResult("加法", 10, 5, add);
PrintResult("减法", 10, 5, subtract);
PrintResult("乘法", 10, 5, multiply);
}
static void PrintResult(
string operationName,
int a,
int b,
Func<int, int, int> operation)
{
int result = operation(a, b);
Console.WriteLine($"{operationName}:{a} 和 {b} 的结果是 {result}");
}
}
输出:
==== 计算器 ====
加法:10 和 5 的结果是 15
减法:10 和 5 的结果是 5
乘法:10 和 5 的结果是 50
这个例子中:
Action<string>用来打印标题。Func<int, int, int>用来表示不同计算规则。PrintResult方法不关心具体怎么算,只负责调用传进来的计算逻辑。
三十一、完整示例:使用 Func 筛选学生
using System;
using System.Collections.Generic;
class Student
{
public string Name { get; set; }
public int Score { get; set; }
}
class Program
{
static void Main()
{
List<Student> students = new List<Student>
{
new Student { Name = "小明", Score = 95 },
new Student { Name = "小红", Score = 82 },
new Student { Name = "小刚", Score = 59 },
new Student { Name = "小丽", Score = 76 }
};
List<Student> passedStudents = Filter(students, s => s.Score >= 60);
List<Student> excellentStudents = Filter(students, s => s.Score >= 90);
Console.WriteLine("及格学生:");
PrintStudents(passedStudents);
Console.WriteLine("优秀学生:");
PrintStudents(excellentStudents);
}
static List<Student> Filter(List<Student> students, Func<Student, bool> condition)
{
List<Student> result = new List<Student>();
foreach (Student student in students)
{
if (condition(student))
{
result.Add(student);
}
}
return result;
}
static void PrintStudents(List<Student> students)
{
foreach (Student student in students)
{
Console.WriteLine($"{student.Name}:{student.Score}");
}
}
}
这里的核心是:
Func<Student, bool> condition
它表示:
传入一个学生,返回这个学生是否满足条件。
所以我们可以传入不同规则:
s => s.Score >= 60
s => s.Score >= 90
同一个 Filter 方法就能完成不同筛选任务。
三十二、课堂讲解建议
讲 Action 和 Func 时,可以按照这个顺序:
- 先讲委托是“保存方法的变量”。
- 再讲为什么需要内置委托。
- 讲
Action:没有返回值。 - 讲
Func:有返回值,最后一个泛型参数是返回值。 - 用 Lambda 写短示例。
- 用方法参数讲回调。
- 用 LINQ 讲实际应用。
- 最后讲闭包、异步和返回值匹配这些注意事项。
学生最容易记混的是:
Func<int, string>
这不是返回 int,而是:
参数是
int,返回值是string。
可以反复强调:
Func最后一个类型永远是返回值类型。
三十三、练习题
练习 1:使用 Action 打印欢迎语
定义一个 Action<string>,传入姓名并打印欢迎语。
参考答案:
Action<string> welcome = name =>
{
Console.WriteLine("欢迎你," + name);
};
welcome("小明");
练习 2:使用 Func 计算平方
定义一个 Func<int, int>,传入一个整数,返回它的平方。
参考答案:
Func<int, int> square = x => x * x;
Console.WriteLine(square(6)); // 36
练习 3:使用 Func 判断偶数
定义一个 Func<int, bool>,判断一个数字是否是偶数。
参考答案:
Func<int, bool> isEven = n => n % 2 == 0;
Console.WriteLine(isEven(10)); // True
Console.WriteLine(isEven(11)); // False
练习 4:把计算规则作为参数
定义一个方法,接收两个整数和一个 Func<int, int, int>,根据传入的规则计算结果。
参考答案:
static int Calculate(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}
使用:
int result1 = Calculate(10, 5, (x, y) => x + y);
int result2 = Calculate(10, 5, (x, y) => x * y);
练习 5:使用 Func 筛选字符串
从字符串列表中筛选出长度大于 3 的字符串。
参考答案:
List<string> words = new List<string> { "C#", "Java", "Python", "Go" };
Func<string, bool> isLongWord = word => word.Length > 3;
foreach (string word in words)
{
if (isLongWord(word))
{
Console.WriteLine(word);
}
}
输出:
Java
Python
三十四、总结
Action 和 Func 是 C# 中非常常用的内置委托,可以让我们更方便地保存方法、传递方法和执行回调。
可以记住下面几句话:
- 委托是可以保存方法的类型。
Action表示没有返回值的方法。Func表示有返回值的方法。Action<T>中的泛型参数都是方法参数类型。Func<T, TResult>中最后一个泛型参数是返回值类型。- 委托变量可以像普通方法一样调用。
Action常用于执行动作、回调、事件。Func常用于计算结果、判断条件、LINQ 查询。- 短逻辑适合 Lambda,复杂逻辑建议提取成普通方法。
- 异步逻辑更适合用
Func<Task>或Func<Task<T>>。
一句话概括:
Action和Func让方法可以像数据一样被保存、传递和调用,是理解 Lambda、事件、LINQ 和回调的重要基础。