LINQ - Fluent 语法

Fluent 语法

Fluent 语法的灵活性给我们操作数据带来了极大的便利。接下来我们看一看如何链结查询操作符以形成更复杂的查询,并说明扩展方法在此过程中的重要性。

Chaining Query Operators 连接查询操作符

我们展示了两个简单的查询,每个查询都包含一个查询操作符。要构建更复杂的查询,可以向表达式添加附加查询操作符,创建一个链。为了说明这一点,下面的查询提取了所有包含字母“a”的字符串,按长度排序,然后将结果转换为大写:

using System;
using System.Collections.Generic;
using System.Linq;
class LinqDemo
{
    static void Main()
    {
        string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
        
        IEnumerable<string> query = names
            .Where (n => n.Contains ("a"))
            .OrderBy (n => n.Length)
            .Select (n => n.ToUpper());
​
        foreach (string name in query) Console.WriteLine (name);
    }
}

 

注意:(这里很重要,写代码时很容易出现变量作用域的问题)

1 在上面的示例中,变量'n'被私有地限定为每个lambda表达式的作用域。我们可以重新使用标识符n.

2 查询操作符永远不会改变输入序列;相反,它返回一个新的序列。(这与函数式编程范式一致,LINQ的灵感来自于此,Spark 的算子也是这样)。

 

数据流从左到右通过操作符链,因此首先过滤数据,然后排序,然后投影。它的自然线性形状反映了从左到右的数据流,并将lambda表达式与其查询操作符(中缀表示法)放在一起。

如果没有扩展方法,查询将失去流畅性,下面是这些扩展方法的签名:

public static IEnumerable<TSource> Where<TSource> 
 (this IEnumerable<TSource> source, Func<TSource,bool> predicate)
​
public static IEnumerable<TSource> OrderBy<TSource,TKey>
 (this IEnumerable<TSource> source, Func<TSource,TKey> keySelector)
​
public static IEnumerable<TResult> Select<TSource,TResult>
 (this IEnumerable<TSource> source, Func<TSource,TResult> selector)

 

一个完整的 LINQ 查询类似于传送带生产线。

 

下面我们可以逐步构造相同的查询,我们可以使用传统的静态方法语法来调用查询操作符,而不是使用扩展方法语法。例如:

IEnumerable<string> filtered = Enumerable.Where (names, n => n.Contains ("a"));
IEnumerable<string> sorted = Enumerable.OrderBy (filtered, n => n.Length);
IEnumerable<string> finalQuery = Enumerable.Select (sorted, n => n.ToUpper());

 

用传统的方式实现,里面的方法返回结果是外层方法的参数,一团糟是不是。

IEnumerable<string> query =
    Enumerable.Select (
        Enumerable.OrderBy (
            Enumerable.Where (
                names, n => n.Contains ("a")
            ), n => n.Length
        ), n => n.ToUpper()
    );
/******************对比一下扩展方法链**************/
IEnumerable<string> query = names
            .Where (n => n.Contains ("a"))
            .OrderBy (n => n.Length)
            .Select (n => n.ToUpper());

 

结合 Lambda 表达式

回顾一下上面的例子,我们将以下lambda表达式输入Where运算符:

n => n.Contains ("a") // Input type=string, return type=bool.

lambda 表达式在一个 query operator 里总是作用于单个elements 在输入序列中,而不是在整个序列上。也就是说,这个查询操作符 通常 需要你的lambda 表达式 在每一个输入序列的元素上计算一次。

Lambda表达式允许您在查询操作符中提供自己的逻辑。这使查询操作符 活多变,也让底层操作更简单。

这是IEnumerable的完整实现,除了异常处理:

public static IEnumerable<TSource> Where<TSource>
    (this IEnumerable<TSource> source, Func<TSource,bool> predicate)
{
    foreach (TSource element in source)
        if (predicate (element))
            yield return element;
}

 

可以看出标准操作符的实现是用lambda表达式实现Func委托。Func中的类型参数以与lambda表达式相同的顺序出现。也就是说你可以调用 在Enumerable中的查询操作符 通过引用的方式使用传统的委托, 而代替 lambda 表达式。

注意,它不适用于基于iquerizable <T>的序列,(例如,在查询数据库时),因为querizable中的操作符需要lambda表达式才能发出表达式树。

 

Lambda 表达式 And element 类型

标准操作符使用以下类型参数名:

public static IEnumerable<TResult> Select<TSource,TResult>
    (this IEnumerable<TSource> source, Func<TSource,TResult> selector)

一个输入元素对应一个输出元素,TSource 和 TResult 可以是不同的类型,所以lambda 表达式可以更变每个元素的类型。更进一步说,lambda 表达式可以决定这个输出序列类型 

 

下面的查询使用Select将字符串类型元素转换为整数类型元素:

string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
IEnumerable<int> query = names.Select (n => n.Length);
foreach (int length in query)
Console.Write (length + "|"); // 3|4|5|4|3|

 

编译器可以从lambda表达式的返回值推断TResult的类型。在这种情况下,n.Length返回一个int值,因此TResult被推断为int。这种方法可以有效地简化某些类型的本地查询—特别是使用LINQ to XML

 

Natural Ordering

在LINQ中,输入序列中元素的原始顺序非常重要。一些查询操作符依赖于这种顺序,比如Take、Skip和Reverse。

Take操作符输出前x个元素,丢弃其余的:

int[] numbers = { 10, 9, 8, 7, 6 };
IEnumerable<int> firstThree = numbers.Take (3); // { 10, 9, 8 }

 

Skip 操作符忽略前x个元素,输出其余的:

IEnumerable<int> lastTwo = numbers.Skip (3); // { 7, 6 }

 

 

Other Operators

不是所有查询操作符都返回一个序列,element 操作符从输入序列中提取一个元素;

例如: First, Last , and ElementAt:

int[] numbers = { 10, 9, 8, 7, 6 };
int firstNumber = numbers.First(); // 10
int lastNumber = numbers.Last(); // 6
int secondNumber = numbers.ElementAt(1); // 9
int secondLowest = numbers.OrderBy(n=>n).Skip(1).First(); // 7
 

 

aggregation 操作符 (聚合) 返回一个 scalar value, 通常是数值型的。

int count = numbers.Count(); // 5;
int min = numbers.Min(); // 6;

 

Concat 、Union 操作符

int[] seq1 = { 1, 2, 3 };
int[] seq2 = { 3, 4, 5 };
IEnumerable<int> concat = seq1.Concat (seq2); // { 1, 2, 3, 3, 4, 5 }
IEnumerable<int> union = seq1.Union (seq2); // { 1, 2, 3, 4, 5 }
 

 

posted on 2019-09-11 21:41  拾掇的往昔  阅读(405)  评论(0编辑  收藏  举报

导航