LINQ的Deferred Execution可能导致的坑

    用LINQ时被Deferred Execution的特性给坑了,于是写文章来纪录下>_<

    Deferred Execution简单来说就是写一个不包含强制马上执行命令(如:Count, Max等)的LINQ表达式时,它只会将表达式保存起来。真正执行的时机则是在真正使用时。具体例子见下图:

    18行中的result没有保存结果,反而保存query本身。如果不明白“保存query本身”的真正含义,很容易被坑。以下是我被坑的简化版(不妨手算下输出,看是否会被坑):

 

坑一

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Diagnostics;
 4 using System.IO;
 5 using System.Linq;
 6 
 7 public class Class1
 8 {
 9     static void Main(string[] args)
10     {
11         var allData = new List<Tuple<int, string>>() {
12             new Tuple<int, string>(0, "Zero"),
13             new Tuple<int, string>(1, "and"),
14             new Tuple<int, string>(2, "Feng"),
15             new Tuple<int, string>(3, "yan")
16         };
17 
18         var t = 0;
19         var result = allData.Where(tuple => tuple.Item1 != t);
20 
21         t = 1;
22         foreach (var r in result)
23         {
24             Console.WriteLine("The " + (t++) + " item: " + r.Item2);
25         }
26         Console.ReadLine();
27     }
28 }

    实际输出如下:

    预期输出应该不包含"Zero"的,那么为什么实际输出会包含呢?原因在于19行的result保存query本身,而这个query包含一个外部变量。详细分析如下:

foreach语句第一次执行时,"query“实际上是”allData.Where(tuple => tuple.Item1 != 1)","Zero"符合。

foreach语句第二次执行时,"query“实际上是”allData.Where(tuple => tuple.Item1 != 2)","and"符合。

foreach语句第三次执行时,"query“实际上是”allData.Where(tuple => tuple.Item1 != 3)","Feng"符合。

foreach语句第四次执行时,"query“实际上是”allData.Where(tuple => tuple.Item1 != 4)","yan"符合。

    解决这坑的方法是强制query马上执行。即将19行变为

var result = allData.Where(tuple => tuple.Item1 != t).ToList();

    此时就能得到期望的结果了。为了加深理解,可以判断下面的输出:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

public class Class1
{
    static void Main(string[] args)
    {
        var allData = new List<Tuple<int, string>>() {
             new Tuple<int, string>(0, "Zero"),
             new Tuple<int, string>(1, "and"),
             new Tuple<int, string>(2, "Feng"),
             new Tuple<int, string>(3, "yan")
         };

        var t = 0;
        var result = allData.Where(tuple => tuple.Item1 == t);

        foreach (var r in result)
        {
            Console.WriteLine(r.Item2);
            t += 2;
        }
        Console.ReadLine();
    }
}

    解释(字体为白色):

foreach语句第一次执行时,"query“实际上是”allData.Where(tuple => tuple.Item1 == 0)","Zero"符合。

foreach语句第二次执行时,"query“实际上是”allData.Where(tuple => tuple.Item1 == 2)","and"不符合,”Feng"符合

foreach语句第三次执行时,"query“实际上是”allData.Where(tuple => tuple.Item1 == 4)","yan"不符合,allData没有元素了,终止。

    所以实际输出为:

Zero

Feng

 

坑二

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

public class Class1
{
    public static void Main(string[] args)
    {
        var allData = new List<Tuple<int, string>>() {
            new Tuple<int, string>(0, "Zero"),
            new Tuple<int, string>(1, "and"),
            new Tuple<int, string>(2, "Feng"),
            new Tuple<int, string>(3, "yan")
        };

        var result = allData.Where(tuple => tuple.Item1 != 0);
        output(result);

        allData.RemoveAt(3);
        output(result);
        Console.ReadLine();
    }

    private static void output(IEnumerable<Tuple<int, string>> data)
    {
        foreach (var d in data)
        {
            Console.WriteLine(d.Item2);
        }
        Console.WriteLine("************");
    }
}

    实际输出如下

    这个输出说明即使query已经执行过一次,也不会保存结果。当再次使用query时,query会重新执行,并且使用当前的data source执行。

    那为什么会这样呢?因为query保存的是query本身。也许你会说,这样做低效。嗯,在以上情景的确如此。但看下MS对LINQ的介绍就会理解MS为什么会选择这个”低效“的做法:

Language-Integrated Query (LINQ) is a set of features introduced in Visual Studio 2008 that extends powerful query capabilities to the language syntax of C# and Visual Basic. LINQ introduces standard, easily-learned patterns for querying and updating data, and the technology can be extended to support potentially any kind of data store. Visual Studio includes LINQ provider assemblies that enable the use of LINQ with .NET Framework collections, SQL Server databases, ADO.NET Datasets, and XML documents.

    MS想实现在非database的情景中也能使用类似于sql语句的功能。那么同一个sql语句在database改变的情形下,自然应该得到不同的结果。当想要得到同一结果时,那么自然不应该让保存query,而应该保存结果,即强制query马上执行。

 

总结

    在使用非马上执行的LINQ语句时,得到的是一个query。每次用到query时,query都会重新执行,并且执行时用的是当前的data source和外部变量的值,而不是定义这个query时这些变量的值。以下是两篇扩展阅读,以兴趣可看下:

LINQ and Deferred Execution

Don’t be too lazy – LINQ’s lazy evaluation gotchas

 

 

转载于:https://www.cnblogs.com/FengYan/archive/2013/05/11/3071653.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值