在C#中,Lambda表达式是一种紧凑的匿名函数写法,可以用来创建委托或表达式树。它从C# 3.0开始引入,目的是简化之前只能用delegate关键字写的匿名方法,并支撑LINQ查询语法。理解Lambda和匿名方法的关系,能帮助我们更自然地操作集合与数据流。

一、匿名方法的基本写法
在Lambda出现之前,C#使用delegate关键字定义匿名方法。匿名方法允许在不需要单独声明方法名的情况下,把一段逻辑直接赋给委托变量。这种方式在事件处理或临时回调中非常实用,但语法相对啰嗦。
下面代码展示了如何用匿名方法创建一个接受两个int参数并返回和的委托:
using System;
class Program
{
static void Main()
{
// 使用匿名方法创建委托
Func<int, int, int> add = delegate(int a, int b)
{
return a + b;
};
Console.WriteLine(add(3, 5)); // 输出 8
}
}
从上面可以看出,匿名方法必须显式写出delegate关键字和参数类型。虽然它避免了定义独立方法,但当逻辑很简单时,这种写法显得不够轻量。另外,匿名方法无法直接转为表达式树,只能在运行时作为委托执行。
二、Lambda表达式的语法形式
Lambda表达式用 => 符号连接参数列表和方法体,编译器会根据上下文推断参数类型。它既能写成表达式体(单行返回值),也能写成语句块(多行逻辑),使用上比匿名方法更灵活。
以下示例用Lambda重写了上面的加法委托,并演示了无参数、单参数和多参数的几种形式:
using System;
class Program
{
static void Main()
{
// 多参数语句块 Lambda
Func<int, int, int> add = (a, b) =>
{
return a + b;
};
// 单参数表达式 Lambda,类型由编译器推断
Func<int, int> square = x => x * x;
// 无参数 Lambda
Action printHello = () => Console.WriteLine("Hello");
Console.WriteLine(add(3, 5));
Console.WriteLine(square(4));
printHello();
}
}
当Lambda只有单个参数时,括号可以省略;方法体若只有一条返回语句,可写成表达式形式而不写return。这种简洁性让Lambda非常适合作为高阶函数的入参。需要注意的是,如果委托目标类型是表达式树(如Expression<Func<int,bool>>),Lambda会被编译为数据结构而非可执行IL,这正是LINQ to SQL等场景的基础。
Lambda与匿名方法的区别
二者核心区别在于:匿名方法只能用delegate声明且不能用于表达式树;Lambda可被编译器转换为委托或表达式树,并支持更自然的类型推断。在旧代码中还能见到匿名方法,但新项目基本都用Lambda。
| 特性 | 匿名方法 | Lambda表达式 |
|---|---|---|
| 关键字 | delegate | => |
| 类型推断 | 需显式写参数类型 | 多数情况可推断 |
| 表达式树支持 | 不支持 | 支持 |
三、在LINQ中的典型应用
LINQ大量依赖Lambda作为查询运算符的参数。无论是基于IEnumerable的内存查询,还是基于IQueryable的数据库查询,Lambda都用来描述筛选、投影、排序等逻辑。下面以List操作为例展示常见用法。
假设有一个学生集合,我们要筛选出分数大于80的学生姓名,并按分数降序排列后输出:
using System;
using System.Collections.Generic;
using System.Linq;
class Student
{
public string Name { get; set; }
public int Score { get; set; }
}
class Program
{
static void Main()
{
List<Student> students = new List<Student>
{
new Student { Name = "张三", Score = 90 },
new Student { Name = "李四", Score = 75 },
new Student { Name = "王五", Score = 85 }
};
// Where 用 Lambda 筛选,Select 投影,OrderByDescending 排序
var result = students
.Where(s => s.Score > 80)
.OrderByDescending(s => s.Score)
.Select(s => s.Name);
foreach (var name in result)
{
Console.WriteLine(name);
}
// 输出:张三 王五
}
}
在上面的链式调用中,s => s.Score > 80是一个返回bool的Lambda,作为Where的筛选条件;s => s.Score提取排序键;s => s.Name把对象转为姓名字符串。这种写法比早期foreach加临时列表的代码短得多,也更容易阅读。
如果换成匿名方法,同样的逻辑会写成delegate(Student s) { return s.Score > 80; },不仅冗长,而且在IQueryable场景下根本无法编译。因此Lambda几乎是LINQ不可或缺的一部分。
Lambda捕获外部变量
Lambda可以捕获方法内的局部变量,这点和匿名方法一致。但要注意,被捕获的变量生命周期会延长到委托被回收为止,若在不经意间形成长生命周期引用,可能导致内存占用偏高。
using System;
class Program
{
static void Main()
{
int threshold = 60;
Func<int, bool> isPass = score => score >= threshold;
Console.WriteLine(isPass(75)); // True
}
}
上述代码中threshold被Lambda捕获,编译器会生成一个隐藏类来保存它。理解这一点有助于排查闭包相关的性能或逻辑问题。
四、常见写法误区与建议
新手常把Lambda体写成多行却忘了加大括号和return,或者把本该用于表达式树的Lambda误用在委托场景。建议明确变量目标类型:如果是List等内存集合,用Func或Action委托即可;如果传给EF Core等ORM,要确保Lambda能被翻译成SQL,避免调用本地方法。
另外,当逻辑较复杂时,不要把过长代码塞进Lambda,可以提取为命名方法再传入,保证可读性。匿名方法如今仅在维护老代码时可见,新开发统一使用Lambda能减少语法分歧。