CodeGym /课程 /C# SELF /lambda 表达式 与 Func, ...

lambda 表达式 与 Func, Action, Predicate

C# SELF
第 50 级 , 课程 1
可用

1. 引言

知道吗?lambda 表达式本身就像一份没有锅的菜谱。它描述了要做什么,但需要一个“容器”才能在代码里存在。在 C# 里,这个容器就是现成的通用委托类型:Func, ActionPredicate

把它们想象成给你的 lambda 的模具——拿合适的模具,把你的逻辑倒进去就行了。没必要自己造轮子或定义自有委托类型,大多数情况下标准的已经够用了。

一点历史

一开始,如果你想把函数作为参数传递,就得声明自己的委托类型。那很冗长、很繁琐,看起来像这样:

delegate int Calculate(int x, int y);

Calculate adder = (a, b) => a + b;

当 C# 引入了 Func, ActionPredicate 后,90% 的场景就不用手动声明委托了。现在看起来简单多了、更通用:

Func<int, int, int> adder = (a, b) => a + b;

2. Func<T, TResult> — 有返回值的函数

语法和用途

Func 是一个泛型委托,它可以接受从 016 个参数,并返回一个值。

Func<int, int, int> sum = (x, y) => x + y;

Func<类型1, 类型2, ..., 类型N, TResult> —— 最后的类型是返回类型,之前的都是参数类型。

示例

1. 两数相加

Func<int, int, int> sum = (x, y) => x + y;
Console.WriteLine(sum(3, 5)); // 8

2. 求平方

Func<int, int> square = x => x * x;
Console.WriteLine(square(4)); // 16

3. 无参数

Func<string> greet = () => "你好,lambda!";
Console.WriteLine(greet());

可视化示意图

签名 示例 说明
Func<int, int>
x => x * 2
接受 int,返回 int
Func<int, int, int>
(a, b) => a + b
两个 int,返回 int
Func<string>
() => "hi"
不接受参数,返回字符串

在你的应用里长什么样

举个例子,在我们的小应用(假设的“用户手册”)里,有一个数字列表——想对它做不同的处理。比如求平方或跟固定数字相加:

List<int> numbers = new() { 1, 2, 3, 4, 5 };

Func<int, int> square = x => x * x;
var squares = numbers.Select(square);
Console.WriteLine(string.Join(", ", squares)); // 1, 4, 9, 16, 25

3. Action<T> — 无返回值的动作

Action 是用来表示那些执行某些操作(例如打印到控制台)但不返回值的方法的通用委托。

它可以接受从 016 个参数,但总是返回 void

示例

1. 打印到控制台

Action<string> print = text => Console.WriteLine("数据: " + text);
print("你好,世界!");

2. 无参数动作

Action greet = () => Console.WriteLine("欢迎!");
greet();

3. 多参数的动作

Action<int, int> showSum = (a, b) => Console.WriteLine($"总和: {a + b}");
showSum(2, 3); // 总和: 5

可视化示意图

签名 示例 说明
Action
() => ...
无参数,无返回
Action<int>
x => ...
一个参数
Action<int, string>
(x, s) => ...
多个参数

在我们的应用中

给“用户手册”加一个打印所有名字的方法:

List<string> names = new() { "安娜", "鲍里斯", "维卡" };
Action<string> printName = name => Console.WriteLine("用户: " + name);

names.ForEach(printName);
// 或者这样: names.ForEach(name => Console.WriteLine(name));

4. Predicate<T> — 是或否?

当你需要一个函数,对某个参数只返回 truefalse 时,就用 Predicate<T>。它就是接受一个参数并返回 bool 的委托。

Predicate —— 官方的“我们需要布尔检查”包装器,专门用来写这种 lambda。

示例

1. 判断是否大于 5

Predicate<int> isGreaterThanFive = x => x > 5;

Console.WriteLine(isGreaterThanFive(3)); // false
Console.WriteLine(isGreaterThanFive(7)); // true

2. 与 List<T>.Find 一起使用

List<int> values = new() { 2, 4, 7, 10 };
int found = values.Find(isGreaterThanFive); // 使用的是 Predicate<int>
Console.WriteLine(found); // 7

3. 全部是成年人吗?

List<int> ages = new() { 12, 19, 34 };

bool allAdults = ages.TrueForAll(age => age >= 18);
// TrueForAll 接受 Predicate<int>

Func<T, bool> 有何区别?

实际上,它们在功能上几乎等价。就连 Microsoft 文档也说:"Predicate<T> 只是为特殊 API 提供的 Func<T, bool>。"

但标准库里有些方法明确要求 Predicate,所以有时还是要区分两者。

5. lambda 怎么“匹配”到 Func, Action, Predicate

当你写一个 lambda,C# 会分析它的形状:"哦,这个形式和目标委托匹配——可以替换进去!"

Func<int, int> f1 = x => x * 2;
Action<string> a1 = text => Console.WriteLine(text);
Predicate<int> p1 = x => x < 10;

到处都是 lambda!但底层其实是三个不同签名的委托类型。

在“真实”代码里的应用

List<User> users = new() {
    new User("安娜", 24),
    new User("鲍里斯", 17),
    new User("维卡", 31),
};

// 返回只有成年用户的函数 (Predicate<User>)
List<User> adults = users.FindAll(user => user.Age >= 18);
Console.WriteLine("成年用户列表: " + string.Join(", ", adults.Select(u => u.Name)));

如果想用 Action<User> 输出所有用户的名字:

users.ForEach(user => Console.WriteLine(user.Name));

获取它们的名字 (Func<User, string>):

IEnumerable<string> names = users.Select(user => user.Name);

一张对比表

委托 签名 lambda 示例 使用场景
Func<T, U>
T → U
user => user.Name
Select, 各种转换
Action<T>
T → void
user => Console.WriteLine(...)
ForEach, 执行动作的方法
Predicate<T>
T → bool
user => user.Age > 18
Find, Exists, 过滤器

6. 带内部应用的示例:一步步来

我们来扩展一下小应用“用户手册”。假设有一个类 User

public class User
{
    public string Name { get; }
    public int Age { get; }
    public bool IsActive { get; set; }

    public User(string name, int age)
    {
        Name = name;
        Age = age;
        IsActive = true;
    }
}

1. Func<User, bool> — 检查用户是否成年

Func<User, bool> isAdult = user => user.Age >= 18;

在 LINQ 中使用:

var adults = users.Where(isAdult);

2. Predicate<User> — 查找活跃用户

Predicate<User> isActive = user => user.IsActive;
User found = users.Find(isActive);

3. Action<User> — 将用户设为不活跃

Action<User> deactivate = user => user.IsActive = false;
users.ForEach(deactivate);

4. Func<User, string> — 获取简短描述

Func<User, string> describe = user => $"{user.Name} ({user.Age})";
var descriptions = users.Select(describe);

这些 lambda 就是“作为数据的代码”,可以传给方法、存在变量里、组合使用。

7. 不明显的细节和常见错误

1. lambda 必须匹配委托签名。 如果签名不匹配,会编译错误。

Func<int, string> wrong = x => x * 2; // 错误: 期待返回 string, 实际返回 int
// 正确写法:
Func<int, string> right = x => (x * 2).ToString();

2. 别忘了 void 和 return 的区别。 Action 不返回值——像 Action<int> a = x => x * x; 这种写法不能工作,因为表达式返回了值,但 Action 不允许返回值。

3. Predicate<T>Func<T, bool> 常常可以互换,但并非总是。 有时集合方法明确要求 Predicate<T>,有时要求 Func<T, bool>。直接赋值可能不行,需要显式包装。

Predicate<int> pred = x => x > 0;
Func<int, bool> func = pred; // 错误
// 但是:
Func<int, bool> func2 = x => x > 0;
Predicate<int> pred2 = new Predicate<int>(func2); // 可以通过构造器转换
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION