TOC

This article is currently in the process of being translated into Chinese (~92% done).

LINQ:

Filtering data: the Where() method

在一组数据中,你可以执行的最基础同时也是最强大的操作就是筛选掉一些数据。在先前的 LINQ 的简介文章中我们已经看到了 Where() 方法能做什么,在本文中,我们将进一步进行研究。我们已经讨论了大量的 LINQ 方法可以使用 Lambda 表达式来执行相应的任务,而 Where() 方法就是其中之一。这些方法会将每项数据作为输入,之后你只需要提供相应逻辑来决定该项数目是否应该包含在最终结果之中(返回 true)或者排除在最终结果之外(返回 false)。下面是一个最基本的例子:

List<int> numbers = new List<int>()
{
    1, 2, 4, 8, 16, 32
};
var smallNumbers = numbers.Where(n => n < 10);
foreach (var n in smallNumbers)
    Console.WriteLine(n);

在这个例子中,我们使用表达式来检测每一个数字,如果该数字小于 10 则返回 true,反之大于等于 10 则返回 false。最终,我们会得到原始列表的一个新版本,在这个新版本中,数值小于 10 的数字才会被囊括其中,最终输出到控制台上。

但是,这并不是说表达式就必须得这么简单。我们可以很轻易地向表达式内添加更多的判断,使得表达式看起来更像一个普通的 if 语句:

List<int> numbers = new List<int>()
{
    1, 2, 4, 8, 16, 32
};
var smallNumbers = numbers.Where(n => n > 1 && n != 4 &&  n < 10);
foreach (var n in smallNumbers)
    Console.WriteLine(n);

这里,我们指定数字必须得大于 1,但不能为 4,且小于 10。

当然,你可以在表达式中使用不同的方法,只要这些方法的返回值属于布尔类型即可。这样,Where() 方法知道当前进行判断的数据项是否应当被包括其中。继续往下看,又是一个新例子:

List<int> numbers = new List<int>()
{
    1, 2, 4, 7, 8, 16, 29, 32, 64, 128
};
List<int> excludedNumbers = new List<int>()
{
    7, 29
};
var validNumbers = numbers.Where(n => !excludedNumbers.Contains(n));
foreach (var n in validNumbers)
    Console.WriteLine(n);

在本例中,我们声明了第二个数字列表,该列表的作用是维护了一组我们不想要被包含进去的数字黑名单。在 Where() 方法中,我们使用 Contains() 方法检查黑名单,来决定每个待检查的数字是否应该进入到最终结果列表中。

理所应当地,这些功能可以作用在比数字和字符串更加复杂的对象上,并且也非常易于使用。看下面这个例子,在这个例子中,我们使用用户信息对象来替代原先的数字类型,并且使用 Where() 方法来获取一组名字以 “J” 开头且年龄不超过39岁的用户列表。

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqWhere2
{
    class Program
    {
static void Main(string[] args)
{
    List<User> listOfUsers = new List<User>()
    {
new User() { Name = "John Doe", Age = 42 },
new User() { Name = "Jane Doe", Age = 34 },
new User() { Name = "Joe Doe", Age = 8 },
new User() { Name = "Another Doe", Age = 15 },
    };

    var filteredUsers = listOfUsers.Where(user => user.Name.StartsWith("J") && user.Age < 40);
    foreach (User user in filteredUsers)
Console.WriteLine(user.Name + ": " + user.Age);
}


class User
{
    public string Name { get; set; }
    public int Age { get; set; }

}
    }
}

为了方便比较,下面展示了基于查询语法而不是基于方法语法的写法:

// Method syntax
var filteredUsers = listOfUsers.Where(user => user.Name.StartsWith("J") && user.Age < 40);

// Query syntax
var filteredUsersQ = from user in listOfUsers where user.Name.StartsWith("J") && user.Age < 40 select user;

链式多 Where() 方法

之前我们在 LINQ 的简介中对此做了简短的讨论:只有等到你确实需要数据时,比如说对查询结果进行循环,或者计算查询结果的个数,或者遍历查询结果(如同我们例子里面那样),LINQ 表达式的实际结果才会得到计算。这也就意味着你可以把多个 Where() 方法链在一起,可能你会觉得更加容易阅读。这在复杂的表达式中确实可以使用。下面是上一个例子的修改版本:

List<int> numbers = new List<int>()
{
    1, 2, 4, 8, 16, 32
};
var smallNumbers = numbers.Where(n => n > 1).Where(n => n != 4).Where(n => n < 10);
foreach (var n in smallNumbers)
    Console.WriteLine(n);

这个例子的结果和之前完全相同。虽然先前的版本可能不够复杂,无法说明拆分成多个 Where() 方法调用的好处,但你可能会遇到想要这样写的情景。我想强调的是,就性能而言,这并不需要额外的性能代价,因为实际上 ”where“ 操作只有等到我们遍历结果才会得到执行,到那时,无论你如果编写查询语句,编译器和解释器都会优化查询语句,使之尽可能快地查询。

概括

使用 Where() 方法,你可以很轻易去除数据源中不想要地数据项,以此来构造一个原始数据集的子集。注意,所获结果一定是一组新的数据集,除非你将新数据集覆盖掉原始数据集,否则原始数据集不会受到任何影响。


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!