1. 引言
在真实项目中,差不多每个程序员都会把大量时间花在集合上:过滤、计数、查找、重新排序、放进拿出——总之,和它们打交道的频率跟睡前盯着冰箱差不多。尤其常见的是提取、处理和聚合数据——不管是用户列表、商品目录、文本行还是其他任何数组。
几乎所有现代的 .NET 集合都支持函数式方法——像 Where、Select、Find、Any、All 等等。它们的厉害之处在于通用性和简洁风格:你只要传入一小段“逻辑”,以 lambda 的形式,集合就像装上了新引擎一样开始工作。
LINQ (Language Integrated Query) 不只是语法糖,它其实是 C# 里的一小套查询语言,让你能像写 SQL 或 Excel 那样对数据发问。但更好的是:直接在代码里写,有自动补全、类型检查和调试。
这一切魔法的背后是委托——每次为了过滤写个独立方法都太累了。lambda 在这就派上用场:作为内联的小函数,把冗长代码变成优雅又表达力强的一行。
2. 集合的标准方法里的 lambda 表达式
lambda 在基于委托的集合方法里表现最好,比如 Find、Exists、ForEach 等等。
示例:按条件查找
假设你有一个商品列表:
using System;
using System.Collections.Generic;
// 我们的商品类
public class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
var products = new List<Product>
{
new Product { Name = "咖啡", Price = 100 },
new Product { Name = "茶", Price = 70 },
new Product { Name = "牛奶", Price = 80 }
};
// 找到第一个昂贵的商品 (>90)
Product expensive = products.Find(p => p.Price > 90); // 用 lambda!
Console.WriteLine(expensive?.Name); // => 咖啡
没有 lambda 的时候你得写单独方法或老式匿名函数。现在一行就能写清楚,读起来像英语:“Find the product where price > 90”。
示例:检查是否有便宜的商品
bool hasCheap = products.Exists(p => p.Price < 75);
Console.WriteLine(hasCheap); // => True(因为 "茶" 比 75 便宜)
示例:处理所有元素(ForEach)
有时候你想对每个元素做点事:
products.ForEach(p => Console.WriteLine($"{p.Name}: {p.Price} 欧元"));
类比一句话
简单说:集合里的 lambda 就像照片编辑器里的“美化”按钮。点一下——结果立刻漂亮起来!
3. Lambda 和 LINQ:集合的魔法
LINQ 不只是方便,它还是迈入函数式风格的好入口。大多数 LINQ 方法接受委托——所以 lambda 是它们的最佳搭档。
用 Where 过滤
还是那份商品列表,来筛出“便宜”的:
using System.Linq;
var cheapProducts = products.Where(p => p.Price < 90);
foreach (var p in cheapProducts)
Console.WriteLine(p.Name); // 茶, 牛奶
你得到了一份新集合,连显式循环都没写。Where 接受一个返回 true/false 的谓词 lambda,并对每个元素应用它。
用 OrderBy 排序
爱整齐的人看这里:
var sorted = products.OrderBy(p => p.Price);
foreach (var p in sorted)
Console.WriteLine($"{p.Name}: {p.Price}");
// 茶: 70
// 牛奶: 80
// 咖啡: 100
映射(Select)——数据投影
有时候我们不需要整个对象,只想要某个字段,比如商品名称列表:
var names = products.Select(p => p.Name);
foreach (var name in names)
Console.WriteLine(name); // 咖啡, 茶, 牛奶
LINQ 链式调用
LINQ 的好处是可以把操作串起来:
var namesOfCheap = products
.Where(p => p.Price < 90)
.OrderBy(p => p.Name)
.Select(p => p.Name.ToUpper());
foreach (var name in namesOfCheap)
Console.WriteLine(name); // 牛奶, 茶
像装配线一样:每个方法是一个处理阶段,输出给下一个阶段。
问题:为什么 lambda 比普通方法更适合 LINQ?
第一,lambda 可以写在用到它的地方。第二,lambda 简短且可读。第三,这是现代 C# 的标准写法——大家都这样写,不这么写的人面试时通常吃亏。
4. 实践示例
在课程中我们做了一个小型练习应用来管理商品、用户或订单。现在给它加上现代的集合处理方法。
按用户名查找用户
public class User
{
public string Username { get; set; }
public int Age { get; set; }
}
var users = new List<User>
{
new User{ Username = "Alice", Age = 21 },
new User{ Username = "Bob", Age = 26 },
new User{ Username = "Charlie", Age = 32 }
};
// 根据名字查找用户
User found = users.FirstOrDefault(u => u.Username == "Bob");
Console.WriteLine(found?.Age); // 26
按年龄过滤
var adults = users.Where(u => u.Age >= 18);
foreach (var u in adults)
Console.WriteLine(u.Username); // Alice, Bob, Charlie
统计用户数量
int count = users.Count(u => u.Age > 25);
Console.WriteLine(count); // 2 (Bob 和 Charlie)
检查是否所有用户都成年
bool allAdults = users.All(u => u.Age >= 18);
Console.WriteLine(allAdults); // True
有没有未成年用户?
bool hasMinor = users.Any(u => u.Age < 18);
Console.WriteLine(hasMinor); // False
5. LINQ:内部是怎么工作的
当你写 Where(u => u.Age > 20) 的时候,实际做的事情和创建一个遍历所有元素并对每个元素检查条件的循环差不多。只不过 LINQ 把这个工作藏起来,并用委托包装你的谓词。
要不是 lambda,你得写这种结构:
public static bool AgeMoreThan20(User u) => u.Age > 20;
var adultUsers = users.Where(AgeMoreThan20);
或者用老式的匿名方法:
var adultUsers = users.Where(delegate(User u) { return u.Age > 20; });
这些写法都比较啰嗦又不够优雅。lambda 看起来更简洁、现代、易读。
6. 委托和常用类型: Func, Action, Predicate
不仅 LINQ 爱 lambda。很多集合方法也接受专用委托,比如:
- Predicate<T> — 用于方法 Find、Exists、RemoveAll
- Func<T, TResult> — 用于 LINQ 方法、投影、计算
- Action<T> — 用于对元素做事但不返回值的方法(比如 ForEach)
实战看起来像这样:
// Predicate<T>
users.RemoveAll(u => u.Age < 30); // 删除了所有小于 30 岁的用户
// Func<T, TResult>
var names = users.Select(u => u.Username);
// Action<T>
users.ForEach(u => Console.WriteLine(u.Username));
7. 集合方法 + lambda 的速查表
| 方法 | 作用 | 委托类型 | Lambda 示例 |
|---|---|---|---|
|
过滤元素 | |
|
|
投影 / 转换 | |
|
|
按键排序 | |
|
|
符合条件的第一个元素 | |
|
|
是否存在至少一个符合条件的元素 | |
|
|
所有元素是否都满足条件 | |
|
|
满足条件的元素数量 | |
|
|
对每个元素执行某操作 | |
|
|
按谓词删除所有元素 | |
|
8. 常见错误和注意点
最常见的错误之一是忘了 LINQ 不会修改原集合,它返回一个新的序列。也就是说,执行 var sorted = users.OrderBy(u => u.Age); 之后,users 这个集合本身仍然保持原来的顺序!这点很容易让人困惑:看起来好像已经排好序了,但实际上并没有。
还有一点:像 Where、Select 之类的方法返回的是 IEnumerable<T>。这是一个“惰性”集合——真正的处理会在你开始枚举它的时候发生(比如用 foreach、调用 ToList() 或 ToArray() 等)。所以如果你想把结果物化,一定记得调用 ToList() 或 ToArray():
var sortedList = users.OrderBy(u => u.Age).ToList();
另外:如果 lambda 捕获了外部作用域的变量(也就是形成了闭包),这些变量会一直存活到对该 lambda 的引用消失为止。通常不用太担心,但如果你在某个长生命周期的对象里使用 lambda 并捕获了“很大的数组”,那么那个数组会因为闭包而被保留在内存中。
最后一个建议:给参数和变量用有意义的名字——这能大大提高可读性,尤其是当你有多层嵌套的 lambda 时。
GO TO FULL VERSION