Lambda 学习笔记
一、什么是 Lambda 表达式
Lambda 表达式是一种简洁的匿名函数表示法,允许用更少的代码定义一个方法,尤其适合传递给委托或 LINQ 查询。
在 C# 中,Lambda 表达式本质上是一个匿名方法的简写形式,可以赋值给一个委托类型的变量,或者作为参数传递。
一句话理解:Lambda 就是“没有名字、可以写在参数里的函数”。
二、从命名方法到 Lambda 的演进
1. 传统命名方法
// 定义一个普通方法
static int Add(int a, int b)
{
return a + b;
}
// 使用委托调用
Func<int, int, int> func = Add;
Console.WriteLine(func(3, 5)); // 8
2. 匿名方法(C# 2.0)
Func<int, int, int> func = delegate(int a, int b)
{
return a + b;
};
Console.WriteLine(func(3, 5)); // 8
3. Lambda 表达式(C# 3.0)
Func<int, int, int> func = (a, b) => a + b;
Console.WriteLine(func(3, 5)); // 8
Lambda 表达式用最少的代码实现了相同的功能,符号 => 读作“goes to”或“成为”。
三、Lambda 表达式的语法
Lambda 表达式的基本形式为:
(参数列表) => 表达式或语句块
1. 表达式 Lambda
右侧是一个表达式,直接返回结果,不需要 return 关键字。
// 一个参数,可省略括号
Func<int, int> square = x => x * x;
Console.WriteLine(square(5)); // 25
// 多个参数,括号不能省略
Func<int, int, int> multiply = (a, b) => a * b;
Console.WriteLine(multiply(4, 6)); // 24
2. 语句 Lambda
右侧是代码块,可包含多条语句,需要使用 return 返回结果。
Func<int, int, int> calculate = (a, b) =>
{
int sum = a + b;
return sum * 2;
};
Console.WriteLine(calculate(3, 4)); // 14
3. 无参数
如果没有参数,使用空括号。
Action greet = () => Console.WriteLine("Hello");
greet();
提示:如果只有一个参数,括号可以省略;如果有多个参数或没有参数,括号必须保留。
四、Lambda 与委托的关系
Lambda 表达式本身没有类型,但可以隐式转换为匹配的委托类型或表达式树。必须将 Lambda 赋值给一个委托变量(如 Func、Action)或作为委托参数传递。
// 隐式转换为 Func 委托
Func<int, bool> isEven = n => n % 2 == 0;
Console.WriteLine(isEven(10)); // True
// 作为方法参数传递
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0);
// Where 方法接受一个 Func<int, bool> 委托,这里传入 Lambda
Where 方法的参数类型是 Func<T, bool>,Lambda 会被自动转换为该委托。
五、Lambda 在 LINQ 中的应用
Lambda 表达式是 LINQ 查询的核心,可以轻松对集合进行筛选、排序、投影等操作。
List<int> numbers = new List<int> { 3, 8, 2, 10, 5 };
// 筛选偶数
var evens = numbers.Where(n => n % 2 == 0);
// 结果:8, 2, 10
// 排序
var sorted = numbers.OrderBy(n => n);
// 结果:2, 3, 5, 8, 10
// 投影
var squares = numbers.Select(n => n * n);
// 结果:9, 64, 4, 100, 25
六、闭包与常见陷阱
Lambda 表达式可以捕获外部变量,这种特性称为闭包。捕获的变量在 Lambda 执行时使用的是变量的引用,而不是值。
闭包示例
int multiplier = 3;
Func<int, int> multiplyBy = x => x * multiplier;
Console.WriteLine(multiplyBy(5)); // 15
multiplier = 10;
Console.WriteLine(multiplyBy(5)); // 50,因为捕获的是变量引用
经典陷阱:循环变量捕获
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions)
{
action();
}
// 输出:3 3 3(而不是 0 1 2)
原因:循环变量 i 在整个循环过程中是同一个变量,Lambda 捕获的是它的引用,循环结束时 i 的值为 3。
解决方案:在循环内部复制一个局部变量。
for (int i = 0; i < 3; i++)
{
int temp = i; // 每次迭代创建新变量
actions.Add(() => Console.WriteLine(temp));
}
另外,C# 5.0 之后,foreach 循环变量在每次迭代时都会创建新变量,因此不会出现这个问题。
七、Lambda 与匿名方法对比
| 特性 | 匿名方法 | Lambda 表达式 |
|---|---|---|
| 语法简洁度 | 较冗长 | 极简 |
| 参数类型推断 | 可以显式指定,但不能省略 | 可以省略,由编译器推断 |
| 是否支持表达式体 | 不支持,必须使用语句块 | 支持表达式体和语句块 |
| 是否可用于表达式树 | 不可以 | 可以(当目标类型为表达式树时) |
现代 C# 开发中,Lambda 表达式已经完全取代了匿名方法,建议优先使用 Lambda。
八、Lambda 的常见使用场景
- 作为
Action、Func、Predicate等委托的实例。 - LINQ 查询中的筛选、排序、投影等操作。
- 事件处理器的简化写法。
- 任务并行库(TPL)中的
Task.Run(() => ...)。 - 配置验证或转换逻辑的简短定义。
// 事件订阅使用 Lambda
button.Click += (sender, e) => MessageBox.Show("Clicked");
// Task 使用 Lambda
Task.Run(() => {
// 后台操作
});
九、总结
- Lambda 表达式是匿名方法的简洁写法,用
=>表示。 - 可以赋值给委托类型,或作为参数传递。
- 分为表达式 Lambda 和语句 Lambda 两种形式。
- Lambda 可以捕获外部变量(闭包),需注意循环变量陷阱。
- 在 LINQ、事件、异步编程中广泛使用。
记忆要点:Lambda = 匿名函数 + 类型推断 + 简洁语法。遇到需要临时方法的地方,优先考虑 Lambda。
十、相关面试题
1. 什么是 Lambda 表达式?它和匿名方法有什么区别?
Lambda 表达式是 C# 3.0 引入的匿名函数简写形式,语法更简洁。匿名方法使用 delegate 关键字,必须使用语句块;Lambda 可以使用表达式体,并且参数类型可推断。此外,Lambda 可以转换为表达式树,匿名方法则不可以。
2. Lambda 表达式可以转换为哪些类型?
Lambda 表达式可以隐式转换为匹配的委托类型(如 Func、Action、自定义委托)或表达式树类型(如 Expression<TDelegate>)。转换的目标类型由上下文决定。
3. 什么是闭包?Lambda 捕获变量时有什么注意事项?
闭包是指 Lambda 表达式或匿名方法捕获了外部作用域的变量。捕获的是变量的引用而非值,因此变量后续的修改会影响 Lambda 的执行结果。常见陷阱是循环变量捕获,解决办法是在循环内创建局部副本或使用 foreach(C# 5.0+)。
4. Lambda 表达式中的参数类型可以省略吗?为什么?
可以省略,编译器会根据委托类型推断参数类型。例如 Func<int, int> f = x => x * 2; 中 x 被推断为 int。但如果有歧义或需要显式指定时,也可以写全类型。
5. 什么是表达式树?它与委托有什么不同?
表达式树是将 Lambda 表达式表示为数据结构(树状),可以在运行时解析、修改或转换,常用于 LINQ to SQL、Entity Framework 等场景。委托则是直接编译为可执行代码。两者都可由 Lambda 转换,但目标类型不同。
6. 在 LINQ 中,Lambda 表达式的作用是什么?
Lambda 表达式用于提供查询操作中的筛选条件、排序键、投影逻辑等。例如 Where(n => n > 5) 中的 Lambda 指定了筛选谓词,使得 LINQ 查询简洁且可读性强。
7. 为什么在 `for` 循环中使用 Lambda 会输出重复的最后一个值?如何解决?
因为 for 循环变量在整个循环中是同一个变量,Lambda 捕获的是变量引用,循环结束后变量值为最后一个值。解决方法是在循环内部声明一个新变量并赋值,让每次迭代捕获不同的变量。
8. Lambda 表达式可以没有参数吗?可以有多条语句吗?
可以。无参数使用空括号 () => ...;多条语句使用语句块 { ... },其中需要显式使用 return 返回结果。
9. 如何理解 `=>` 运算符?
=> 称为 Lambda 运算符,读作“goes to”。它将左侧的参数列表和右侧的表达式或语句块分隔开。左侧为输入,右侧为处理逻辑或返回值。
10. Lambda 表达式能否直接赋值给 `var` 变量?为什么?
不能直接赋值给 var,因为 var 需要编译器从右侧推断具体类型,而 Lambda 本身没有类型,需要目标类型(委托或表达式树)才能转换。必须指定委托类型,例如 Func<int, int> f = x => x;。

浙公网安备 33010602011771号