CodeGym /课程 /C# SELF /用 Where过滤数据

用 Where过滤数据

C# SELF
第 31 级, 课程 2
可用

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("牛"));
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION