1. 入门
过滤就是从集合里挑出那些符合你条件的元素。想象一下:你数据库里有10 000个商品,经理只要12个,得满足某个新出的奇葩标准。每次都手写循环和一堆判断,真是太折磨人了,代码也乱七八糟。LINQ帮你搞定这些麻烦事。
在实际项目里,过滤是最常见的数据操作:只给用户展示他关心的记录,把集合里的垃圾元素清掉,做报表、搜索或者发邮件时选出需要的数据。面试时LINQ过滤的题目也老是出现。搞懂这块内容,对刚入门的.NET开发者来说,绝对是成功的关键。
2. 认识Where方法
原理和工作方式
Where方法是LINQ的扩展方法,它接收一个函数(或者lambda表达式),用来定义过滤条件。它会返回一个新的序列(不会改原来的!),里面只包含那些条件返回true的元素。
主要签名:
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
别被generics和这些吓人的词吓到。现在你只要明白核心思想:Where接收一个原始集合和你写的条件。实际用起来很简单:
- source —— 就是你要过滤的集合(比如List<Product>)
- predicate —— 函数/条件(比如p => p.Price > 100)
简单小练习
假设我们有个商品列表,只想选出价格大于100的。以前老办法得写foreach循环,手动把符合条件的加到新列表里。LINQ一句话就能搞定。
3. 过滤
我们的Product类和初始集合
上个模块我们写过类似这样的商品类:
class Product
{
public string Name { get; set; }
public double Price { get; set; }
// 为了让输出更好看:
public override string ToString()
{
return $"{Name} (价格: {Price})";
}
}
假设我们有个商品列表:
var products = new List<Product>
{
new Product { Name = "面包", Price = 30 },
new Product { Name = "牛奶", Price = 87 },
new Product { Name = "奶酪", Price = 250 },
new Product { Name = "巧克力", Price = 130 }
};
用Where过滤价格大于100的商品
var expensiveProducts = products.Where(p => p.Price > 100);
foreach (var product in expensiveProducts)
{
Console.WriteLine(product);
}
底层发生了啥:
- Where遍历整个products列表,
- 对每个元素调用函数(这里是p => p.Price > 100),
- 如果函数返回true,这个商品就进新集合。
屏幕上的结果:
奶酪 (价格: 250)
巧克力 (价格: 130)
注意:原始products列表没变!LINQ不会破坏你的数据。
4. 延迟过滤(Deferred Execution)
有个很有意思也很重要的点:Where的结果,只有你真的去用它时才会被计算。比如你一开始用foreach循环时,LINQ才会“走”原始集合并实时过滤。如果你没用结果,啥都不会发生。这就叫延迟执行(deferred execution)。
这样有很多好处:
- 你可以写很长的链式方法(比如又过滤又排序),只有第一次真正用到结果时才会执行。
- 省内存:不会产生多余的中间集合。
- 需要的话可以随时取消执行,或者找到足够多的元素就“中断”。
可视化流程
原始集合 —► Where (条件) —► 只遍历需要的元素
[ x ] [ x ] [ + ] [ + ] —► p.Price > 100 —► [ + ] [ + ]
(x —— 不符合条件的元素,+ —— 符合条件的)
5. 不同语法写法
经典扩展方法写法(Method Syntax)
我们已经在用这种写法了:
var result = products.Where(p => p.Price > 100);
另一种:Query Syntax
LINQ还支持类似SQL的写法:
var result = from p in products
where p.Price > 100
select p;
结果是一样的!
选哪种语法看你喜欢,但method syntax(点方法)更常见,尤其是实际项目和写长链式操作时。
6. 复杂条件过滤
多个条件
你可以用逻辑运算符(&&,||,!):
// 找出所有贵的商品,除了“巧克力”
var filtered = products.Where(
p => p.Price > 100 && p.Name != "巧克力"
);
按字符串过滤(子串、大小写、Contains)
// 名字里包含“奶”字的商品
var lProducts = products.Where(p => p.Name.Contains("奶"));
注意:Contains是区分大小写的!如果你想“不区分大小写”,可以这样写:
var lProducts = products.Where(p => p.Name
.ToLower().Contains("奶")); // 现在“牛奶”和“奶酪”都会被选中
多集合过滤
假如你有两个列表——商品和支付记录。可以过滤那些出现在支付记录里的商品——不过这时候最好用Any和Join方法,这个以后再讲。你只要知道:LINQ也能搞很复杂的过滤!
7. 有用的小技巧
嵌套过滤和链式方法
你可以多次调用Where,实现多步过滤:
var filtered = products
.Where(p => p.Price > 100)
.Where(p => p.Name.StartsWith("巧"));
不过最好把条件合并到一个Where里,用逻辑运算符,这样效率更高,代码也更清楚。
内置比较器和自定义函数
有时候标准运算符不够用。比如你要不区分大小写、考虑文化差异地过滤字符串。这时可以用比较方法:
var rusProducts = products.Where(
p => p.Name.StartsWith("巧", StringComparison.OrdinalIgnoreCase)
);
8. 过滤和用户输入
来试试做个简单的命令行过滤!比如问用户最小价格,然后显示所有符合条件的商品。
Console.Write("商品的最低价格? ");
var minPriceStr = Console.ReadLine();
if (double.TryParse(minPriceStr, out double minPrice))
{
var filtered = products.Where(p => p.Price >= minPrice);
foreach (var product in filtered)
{
Console.WriteLine(product);
}
}
else
{
Console.WriteLine("错误:输入的不是数字。");
}
现在我们的“小应用”已经有点像个商店啦!
9. 用Where时常见的坑
编程和生活一样,总有坑。下面这些要注意。
忘了调用ToList()或ToArray()
Where的结果不是列表,也不是数组!它是IEnumerable<Product>对象。大多数时候你可以直接用foreach循环遍历,但如果你需要集合(比如要按索引访问),记得加.ToList()或.ToArray():
var filteredList = products.Where(p => p.Price > 100).ToList();
如果忽略了,很容易遇到"集合在枚举时被修改"这种错误——尤其是你在遍历时还改原始集合。
修改原始集合
因为LINQ过滤是延迟执行的,如果你在遍历过程中删原始列表的元素,可能会抛异常。多线程或者过滤时集合被改动时尤其要注意。
Null值和条件函数
如果集合里有null,你在条件里直接访问对象字段没判断,就会NullReferenceException。比如:
var filtered = products.Where(p => p.Name.StartsWith("牛"));
如果products里有元素是null,代码就会崩。
建议:如果有可能有null元素,条件里一定要加null判断:
var filtered = products.Where(p => p != null && p.Name.StartsWith("牛"));
GO TO FULL VERSION