LINQ 中的查询语法和方法语法 (C#)Query Syntax and Method Syntax in LINQ (C#)

介绍性的语言集成查询 (LINQLINQ) 文档中的大多数查询是使用 LINQ 声明性查询语法编写的。Most queries in the introductory Language Integrated Query (LINQLINQ) documentation are written by using the LINQ declarative query syntax. 但是在编译代码时,查询语法必须转换为针对 .NET 公共语言运行时 (CLR) 的方法调用。However, the query syntax must be translated into method calls for the .NET common language runtime (CLR) when the code is compiled. 这些方法调用会调用标准查询运算符(名称为 WhereSelectGroupByJoinMaxAverage 等)。These method calls invoke the standard query operators, which have names such as Where, Select, GroupBy, Join, Max, and Average. 可以使用方法语法(而不查询语法)来直接调用它们。You can call them directly by using method syntax instead of query syntax.

查询语法和方法语法在语义上是相同的,但是许多人发现查询语法更简单且更易于阅读。Query syntax and method syntax are semantically identical, but many people find query syntax simpler and easier to read. 某些查询必须表示为方法调用。Some queries must be expressed as method calls. 例如,必须使用方法调用表示检索与指定条件匹配的元素数的查询。For example, you must use a method call to express a query that retrieves the number of elements that match a specified condition. 还必须对检索源序列中具有最大值的元素的查询使用方法调用。You also must use a method call for a query that retrieves the element that has the maximum value in a source sequence. System.Linq 命名空间中的标准查询运算符的参考文档通常使用方法语法。The reference documentation for the standard query operators in the System.Linq namespace generally uses method syntax. 因此,即使在开始编写 LINQLINQ 查询时,熟悉如何在查询和查询表达式本身中使用方法语法也十分有用。Therefore, even when getting started writing LINQLINQ queries, it is useful to be familiar with how to use method syntax in queries and in query expressions themselves.

标准查询运算符扩展方法Standard Query Operator Extension Methods

下面的示例演示一个简单查询表达式以及编写为基于方法的查询的语义上等效的查询。The following example shows a simple query expression and the semantically equivalent query written as a method-based query.

class QueryVMethodSyntax
{
    static void Main()
    {
        int[] numbers = { 5, 10, 8, 3, 6, 12};

        //Query syntax:
        IEnumerable<int> numQuery1 = 
            from num in numbers
            where num % 2 == 0
            orderby num
            select num;

        //Method syntax:
        IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);

        foreach (int i in numQuery1)
        {
            Console.Write(i + " ");
        }
        Console.WriteLine(System.Environment.NewLine);
        foreach (int i in numQuery2)
        {
            Console.Write(i + " ");
        }
        
        // Keep the console open in debug mode.
        Console.WriteLine(System.Environment.NewLine);
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}
/*
    Output:
    6 8 10 12
    6 8 10 12
 */

这两个示例的输出是相同的。The output from the two examples is identical. 可以看到查询变量的类型在两种形式中是相同的:IEnumerable<T>You can see that the type of the query variable is the same in both forms: IEnumerable<T>.

为了了解基于方法的查询,我们来仔细讨论它。To understand the method-based query, let's examine it more closely. 在表达式右侧,请注意,where 子句现在表示为 numbers 对象上的实例方法,它具有类型 IEnumerable<int>(如同你会回忆起的那样)。On the right side of the expression, notice that the where clause is now expressed as an instance method on the numbers object, which as you will recall has a type of IEnumerable<int>. 如果熟悉泛型 IEnumerable<T> 接口,则会知道它没有 Where 方法。If you are familiar with the generic IEnumerable<T> interface, you know that it does not have a Where method. 但是,如果在 Visual Studio IDE 中调用 IntelliSense 完成列表,则不仅会看到 Where 方法,还会看到许多其他方法(如 SelectSelectManyJoinOrderby)。However, if you invoke the IntelliSense completion list in the Visual Studio IDE, you will see not only a Where method, but many other methods such as Select, SelectMany, Join, and Orderby. 这些都是标准查询运算符。These are all the standard query operators.

Intellisense 中的标准查询运算符Standard Query Operators in Intellisense

虽然看起来似乎 IEnumerable<T> 进行了重新定义以包括这些其他方法,不过实际上情况并非如此。Although it looks as if IEnumerable<T> has been redefined to include these additional methods, in fact this is not the case. 标准查询运算符作为一种新类型的方法(称为扩展方法)来实现。The standard query operators are implemented as a new kind of method called extension methods. 扩展方法可“扩展”现有类型;它们可以如同类型上的实例方法一样进行调用。Extensions methods "extend" an existing type; they can be called as if they were instance methods on the type. 标准查询运算符扩展了 IEnumerable<T>,因此可以写入 numbers.Where(...)The standard query operators extend IEnumerable<T> and that is why you can write numbers.Where(...).

若要开始使用 LINQLINQ,你在扩展方法方面实际需要了解的所有内容是如何使用正确的 using 指令将它们引入应用程序的范围。To get started using LINQLINQ, all that you really have to know about extension methods is how to bring them into scope in your application by using the correct using directives. 从应用程序的角度来看,扩展方法与常规实例方法是相同的。From your application's point of view, an extension method and a regular instance method are the same.

有关扩展方法的详细信息,请参阅扩展方法For more information about extension methods, see Extension Methods. 有关标准查询运算符的详细信息,请参阅标准查询运算符概述 (C#)For more information about standard query operators, see Standard Query Operators Overview (C#). 某些 LINQLINQ 提供程序(如 LINQ to SQLLINQ to SQLLINQ to XMLLINQ to XML),会实现自己的标准查询运算符,并为 IEnumerable<T> 之外的其他类型实现额外的扩展方法。Some LINQLINQ providers, such as LINQ to SQLLINQ to SQL and LINQ to XMLLINQ to XML, implement their own standard query operators and additional extension methods for other types besides IEnumerable<T>.

Lambda 表达式Lambda Expressions

在上面的示例中,请注意,条件表达式 (num % 2 == 0) 作为内联参数传递给 Where 方法:Where(num => num % 2 == 0). 此内联表达式称为 lambda 表达式。In the previous example, notice that the conditional expression (num % 2 == 0) is passed as an in-line argument to the Where method: Where(num => num % 2 == 0). This inline expression is called a lambda expression. 可采用匿名方法、泛型委托或表达式树的形式编写原本必须以更繁琐的形式编写的代码,这是一种便利的方式。It is a convenient way to write code that would otherwise have to be written in more cumbersome form as an anonymous method or a generic delegate or an expression tree. 在 C# 中,=> 是 lambda 运算符(读为“转到”)。In C# => is the lambda operator, which is read as "goes to". 运算符左侧的 num 是输入变量,它与查询表达式中的 num 对应。The num on the left of the operator is the input variable which corresponds to num in the query expression. 编译器可以推断出 num 的类型,因为它知道 numbers 是泛型 IEnumerable<T> 类型。The compiler can infer the type of num because it knows that numbers is a generic IEnumerable<T> type. Lambda 的主体与查询语法中或任何其他 C# 表达式或语句中的表达式完全相同;它可以包含方法调用和其他复杂逻辑。The body of the lambda is just the same as the expression in query syntax or in any other C# expression or statement; it can include method calls and other complex logic. “返回值”就是表达式结果。The "return value" is just the expression result.

若要开始使用 LINQLINQ,不必大量使用 lambda。To get started using LINQLINQ, you do not have to use lambdas extensively. 但是,某些查询只能采用方法语法进行表示,而其中一些查询需要 lambda 表达式。However, certain queries can only be expressed in method syntax and some of those require lambda expressions. 进一步熟悉 lambda 之后,你会发现它们是 LINQLINQ 工具箱中一种强大而灵活的工具。After you become more familiar with lambdas, you will find that they are a powerful and flexible tool in your LINQLINQ toolbox. 有关详细信息,请参阅 Lambda 表达式For more information, see Lambda Expressions.

查询的可组合性Composability of Queries

在前面的代码示例中,请注意,OrderBy 方法通过对 Where 调用使用点运算符来调用。In the previous code example, note that the OrderBy method is invoked by using the dot operator on the call to Where. Where 会生成经过筛选的序列,然后 Orderby 通过进行排序来对该序列进行操作。Where produces a filtered sequence, and then Orderby operates on that sequence by sorting it. 由于查询返回 IEnumerable,因此可通过将方法调用链接在一起在方法语法中撰写查询。Because queries return an IEnumerable, you compose them in method syntax by chaining the method calls together. 这是当你使用查询语法编写查询时,编译器在幕后进行的工作。This is what the compiler does behind the scenes when you write queries by using query syntax. 因为查询变量不存储查询的结果,所以可以随时修改它或将它用作新查询的基础(即使在执行过它之后)。And because a query variable does not store the results of the query, you can modify it or use it as the basis for a new query at any time, even after it has been executed.

请参阅See Also