Lambda 表达式不是语法糖,而是编译器生成 Func 或 Expression<Func> 的正式机制;零参数必须写(),单参数可省略,多参数必须加括号;Func 用于内存操作,Expression 用于 IQueryable 查询;循环中捕获变量需局部副本避免共享引用。

直接说结论:Lambda 表达式不是语法糖,而是编译器生成 Func 或 Expression<Func> 的正式机制——写错类型或用错上下文,程序要么编译失败,要么运行时炸。
什么时候必须写括号 (),什么时候能省略
括号是否可省,只取决于参数个数,和类型推断无关:
- 零参数:必须写
(),写成=> "hello"会编译报错CS0149: 方法名期望 - 单参数:括号可省,
x => x.ToString()和(x) => x.ToString()等价 - 两个及以上参数:括号强制要求,
(a, b) => a + b合法,a, b => a + b直接编译失败
注意:即使参数类型需显式声明,括号也不能丢,(string s, int n) => s.Length * n 是合法写法;string s, int n => ... 不是 C# 语法。
Func<T, R> 和 Expression<Func<T, R>> 别混用
这不是“写法区别”,而是运行模型的根本差异:
- 传给
IEnumerable<T>.Where()、List<T>.FindAll()、Task.Run()等内存操作 API:用Func,直接执行,快且自由 - 传给
IQueryable<T>.Where()(如 Entity Framework、LINQ to SQL):必须用Expression<Func>,否则抛InvalidOperationException: The LINQ expression could not be translated - 常见翻车点:把 lambda 先赋给
Func变量再传给IQueryable,例如Func<Person, bool> f = p => p.Name.StartsWith("A"); db.People.Where(f);—— 这会失败,因为f是已编译委托,EF 拿不到表达式树
安全写法是显式声明类型:Expression<Func<Person, bool>> expr = p => p.Name.StartsWith("A");,或直接传字面量 lambda。
循环中捕获变量的坑:你存的不是值,是变量本身
这是最常被忽略、调试起来最头疼的问题之一:
- 错误写法:
var handlers = new List<Action>(); for (int i = 0; i < 3; i++) handlers.Add(() => Console.WriteLine(i));—— 执行后全部输出3 - 原因:所有 lambda 捕获的是同一个变量
i的引用,循环结束时i == 3,所以每个委托都读到 3 - 修复方式:在循环体内创建局部副本,
for (int i = 0; i < 3; i++) { int localI = i; handlers.Add(() => Console.WriteLine(localI)); } - 延伸风险:若该委托被长期持有(如注册为静态事件处理器),
localI所在的闭包对象就无法被 GC 回收,可能引发内存泄漏
真正难的从来不是怎么写对一个 lambda,而是判断它该被编译成委托还是表达式树,以及它捕获的变量在多大范围内还活着。这两点不厘清,代码越写越像玄学。


















