LINQ中的延迟执行

1.前言

在使用 LINQ 查询的过程中存在着两种查询方式,一种是立即执行,另一种是延迟执行。下面将主要讲解 LINQ 的特殊支持——延迟执行。

2.延迟执行

延迟执行意味着,他们不是在查询创建的时候执行,而是在使用 foreach 语句遍历的时候执行(换句话说,当 GetEnumerator 的 MoveNext 方法被调用时)。现在考虑下面这种查询的实现:

static void Main(string[] args)
{
    List<int> list = new List<int>();
    list.Add(1);
    IEnumerable<int> result =
        from item in list
        select item;
    list.Add(2);
    foreach(var item in result)
    {
        Console.WriteLine(item);
    }
    Console.ReadKey();
}

其输出结果为:

1
2

可以发现,在定义了 LINQ 查询后再向 list 中添加新项 “2”,仍然可以在 froeach 语句中输出 ”2“, 这是因为直到f oreach 语句对 result 进行遍历时,LINQ查询才会执行,这便是 LINQ 的延迟执行。

除了下面两种查询运算符,所有其他的运算符都是延迟执行的:

  • 返回单个元素或者标量值的查询运算符,如First、Count等。
  • 下面这些转换运算符:ToArray、ToList、ToDictionary、ToLookup。

上面两种运算符会被立即执行,因为他们的返回值类型没有提供延迟执行的机制,比如下面的查询会被立即执行。

int matches = list.Where(n => (n % 2) == 0).Count();     // 1

将返回的集合对象分配给一个新变量时,实现的时直接执行,如下面的示例:

static void Main(string[] args)
{
    List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
    IEnumerable<int> result =
        from item in list
        select item;
    List<int> newlist = list.ToList();
    list.Add(6);
    foreach (var item in newlist)
    {
        Console.Write(item + " ");
    }
    Console.ReadKey();
}

其输出结果如下所示,即使在 ToList 后更改 list 中的项,foreach 语句的执行结果依旧不变:

1 2 3 4 5

3.重复执行

但是,延迟执行带来的一个影响是,当我们重复遍历查询结果时,查询会被重复执行,例如下面的示例:

static void Main(string[] args)
{
    var numbers = new List<int>() { 1, 2 };
    IEnumerable<int> query = numbers.Select(n => n * 10);   // Build query
    foreach (int n in query) Console.Write(n + " ");        // 10 20
    numbers.Clear();
    foreach (int n in query) Console.Write(n + " ");        // <nothing>
    Console.ReadKey();
}

显然之前的查询数据会被覆盖,当我们需要保留之前查询结果时,这显然是不满足要求的,这时候可以使用上文中提到的转换运算符 ToArray、ToList、ToDictionary、ToLookup 方法存储查询结果。

4.变量捕获

延迟执行还有一个不好的副作用。如果查询的lambda表达式引用了程序的局部变量时,查询会在执行时对变量进行捕获。这意味着,如果在查询定义之后改变了该变量的值,那么查询结果也会随之改变。

IEnumerable<char> query = "How are you, friend.";
foreach (char vowel in "aeiou")
    query = query.Where(c => c != vowel);
foreach (char c in query) Console.Write(c); //How are yo, friend.

结果 query 中只有 ”u“ 被过滤了,可以修改代码如下:

IEnumerable<char> query = "How are you, friend.";
foreach (char vowel in "aeiou")
{
    char temp = vowel;
    query = query.Where(c => c != temp);
}
foreach (char c in query) Console.Write(c); //Hw r y, frnd.

此时,才能实现对元音字母的过滤。

猜你喜欢

转载自www.cnblogs.com/jizhiqiliao/p/9850178.html