C# 中的 Lambda 表达式(Lambda Expression) 是一种简洁、函数式的语法 ,用于创建匿名函数(即没有名称的方法),通常用于初始化委托(如 Action、Func)或表达式树(Expression Tree)。它是现代 C# 编程的核心特性之一,广泛应用于 LINQ、事件处理、异步编程和函数式风格代码中。
一、基本语法
csharp
(参数列表) => 表达式或语句块
=>读作 "goes to" 或 "映射到"。- 左侧是参数 ,右侧是方法体。
✅ 常见形式示例:
| 形式 | 示例 | 说明 |
|---|---|---|
| 无参 | () => Console.WriteLine("Hello") |
常用于 Action |
| 单参(可省略括号) | x => x * x |
参数类型可推断 |
| 多参 | (x, y) => x + y |
必须加括号 |
| 语句块 | x => { return x > 0 ? "正数" : "非正数"; } |
多行逻辑用 {} |
| 无返回值 | msg => Console.WriteLine(msg) |
相当于 void 方法 |
二、Lambda 与委托的关系
Lambda 表达式本身不是类型,但它可以隐式转换为兼容的委托类型 (如 Action、Func)或表达式树类型 (Expression<T>)。
示例:赋值给 Func 和 Action
csharp
// Func<int, int>:接收 int,返回 int
Func<int, int> square = x => x * x;
Console.WriteLine(square(5)); // 输出 25
// Action<string>:接收 string,无返回
Action<string> greet = name => Console.WriteLine($"Hello, {name}!");
greet("Alice"); // 输出 Hello, Alice!
🔍 编译器会根据目标类型自动推断 Lambda 的参数类型和返回类型。
三、Lambda 的核心优势
✅ 1. 简洁性
相比匿名方法或命名方法,代码更短、意图更清晰:
csharp
// 传统方式
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
List<int> evens = numbers.FindAll(delegate(int n) { return n % 2 == 0; });
// Lambda 方式(推荐)
var evens = numbers.Where(n => n % 2 == 0).ToList();
✅ 2. 闭包(Closure)支持
Lambda 可以捕获外部作用域的变量:
csharp
string prefix = "日志: ";
Action<string> logger = msg => Console.WriteLine(prefix + msg);
logger("程序启动"); // 输出:日志: 程序启动
编译器会自动生成一个"闭包类"来保存
prefix,确保生命周期安全。
✅ 3. 与 LINQ 无缝集成
LINQ 方法(如 Where、Select、OrderBy)大量使用 Lambda:
csharp
var names = new[] { "Alice", "Bob", "Charlie" };
var longNames = names
.Where(name => name.Length > 3)
.Select(name => name.ToUpper())
.ToArray(); // ["ALICE", "CHARLIE"]
✅ 4. 支持表达式树(Expression Tree)
当 Lambda 赋值给 Expression<Func<...>> 时,不会编译为 IL 代码,而是生成可分析的表达式树,用于 ORM(如 Entity Framework):
csharp
Expression<Func<int, bool>> expr = x => x > 10;
// EF 可将此表达式翻译为 SQL: WHERE x > 10
四、Lambda vs 匿名方法 vs 命名方法
| 特性 | 命名方法 | 匿名方法 | Lambda 表达式 |
|---|---|---|---|
| 语法简洁性 | ❌ 冗长 | ⭕ 中等 | ✅ 极简 |
| 可读性(简单逻辑) | ❌ | ⭕ | ✅ |
| 支持表达式树 | ❌ | ❌ | ✅ |
| 类型推断 | ❌ | ❌ | ✅ |
| 现代 C# 推荐度 | 仅复杂逻辑 | 基本淘汰 | ✅ 首选 |
💡 经验法则:
- 简单逻辑 → 用 Lambda;
- 复杂/复用逻辑 → 用命名方法;
- 避免使用匿名方法(除非维护旧代码)。
五、高级用法示例
1. 多行 Lambda(语句块)
csharp
Func<int, string> classify = number =>
{
if (number < 0) return "负数";
if (number == 0) return "零";
return "正数";
};
2. 忽略参数(使用 _)
csharp
Action<object, EventArgs> handler = (_, __) => Console.WriteLine("Clicked!");
button.Click += handler;
3. 递归 Lambda(需先声明变量)
csharp
Func<int, int> factorial = null;
factorial = n => n <= 1 ? 1 : n * factorial(n - 1);
Console.WriteLine(factorial(5)); // 120
六、常见内置委托类型(配合 Lambda 使用)
| 委托 | 签名 | 示例 |
|---|---|---|
Action |
void() |
() => Console.WriteLine("Run") |
Action<T> |
void(T) |
x => Console.WriteLine(x) |
Func<R> |
R() |
() => DateTime.Now |
Func<T, R> |
R(T) |
x => x.ToString() |
Predicate<T> |
bool(T) |
x => x > 0 |
✅ 这些类型让 Lambda 几乎无需自定义委托即可覆盖 99% 场景。
七、注意事项与陷阱
⚠️ 1. 闭包与循环变量(经典陷阱)
csharp
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i)); // 所有输出 3!
}
// 解决方案:引入局部副本
for (int i = 0; i < 3; i++)
{
int local = i;
actions.Add(() => Console.WriteLine(local)); // 正确输出 0,1,2
}
⚠️ 2. 性能考虑
- Lambda 本身开销极小;
- 但频繁创建委托对象(如在循环内)可能增加 GC 压力;
- 表达式树比普通 Lambda 慢(因需解析树结构)。
✅ 总结
| 关键点 | 说明 |
|---|---|
| 是什么 | 创建匿名函数的简洁语法 |
| 核心符号 | => |
| 主要用途 | 初始化委托、LINQ 查询、事件处理、函数式编程 |
| 优势 | 简洁、支持闭包、类型推断、表达式树 |
| 最佳实践 | 优先使用 Action/Func + Lambda,避免匿名方法 |
🌟 一句话记住 :
Lambda 表达式让 C# 拥有了"把行为当作数据传递"的能力,是写出优雅、简洁、现代 C# 代码的关键。