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

直接说结论:Lambda 表达式不是语法糖,而是编译器生成 Func 或 Expression<func></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></t> 和 Expression<func r>></func> 别混用
这不是“写法区别”,而是运行模型的根本差异:
- 传给
IEnumerable<t>.Where()</t>、List<t>.FindAll()</t>、Task.Run()等内存操作 API:用Func,直接执行,快且自由 - 传给
IQueryable<t>.Where()</t>(如 Entity Framework、LINQ to SQL):必须用Expression<func></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);</person>—— 这会失败,因为f是已编译委托,EF 拿不到表达式树
安全写法是显式声明类型:Expression<func bool>> expr = p => p.Name.StartsWith("A");</func>,或直接传字面量 lambda。
循环中捕获变量的坑:你存的不是值,是变量本身
这是最常被忽略、调试起来最头疼的问题之一:
- 错误写法:
var handlers = new List<action>(); for (int i = 0; i Console.WriteLine(i));</action>—— 执行后全部输出3 - 原因:所有 lambda 捕获的是同一个变量
i的引用,循环结束时i == 3,所以每个委托都读到 3 - 修复方式:在循环体内创建局部副本,
for (int i = 0; i Console.WriteLine(localI)); } - 延伸风险:若该委托被长期持有(如注册为静态事件处理器),
localI所在的闭包对象就无法被 GC 回收,可能引发内存泄漏
真正难的从来不是怎么写对一个 lambda,而是判断它该被编译成委托还是表达式树,以及它捕获的变量在多大范围内还活着。这两点不厘清,代码越写越像玄学。










