Linq中的绝大多数查询运算符都有延迟执行的特性,查询并不是在查询创建的时候执行,而是在遍历的时候执行,也就是在enumerator的MoveNext()方法被调用的时候执行,大说数Linq查询操作实例方法返回的都是IEnumerable<T>,所以只有在使用foreach遍历的时候,查询方法才能被真正的执行.请参考
示例代码如下:
List list=new List ();list.AddRange(new int[]{ 1, 43, 5, 7, 8 });IEnumerable result = list.Where(n => n >= 40);list.Add(50);foreach (var n in result){ Console.WriteLine(n);}
输出结果一目了然,当创建完查询之后添加的元素也包含到了结果集中,说明查询并没有立即执行,而是在使用foreach遍历之后才执行,这种特性就是Linq的延迟执行.
不止Where查询操作符是这样的,其他的只要返回的是IEnumerable<T>对象的都有延迟执行特性.
注:其他的一些像First、Count、ToArray、ToList、ToDictionary、ToLookup这些都是立即执行的.
当然,对于Linq来说,延迟执行是非常重要的,因为它把查询的创建和查询的执行解耦了,这让我们可以像创建SQL查询那样,分成多个步骤来创建我们的LINQ查询。
重复执行
使用导致延迟执行的查询操作符进行查询操作,并且两次或者两次以上的使用foreach,会导致查询重复执行,重复执行在以下两种情况下,绝对是不好的:
1、当需要在一个确定点保存查询的结果时,因为延迟执行并不会在创建查询之后马上得到查询结果集,所以必须使用上面提到的ToArray、ToList等方法使查询立即执行得到结果集并进行存储,代码如下:
List list=new List ();list.AddRange(new int[]{ 1, 43, 5, 7, 8 });IEnumerable result = list.Where(n => n >= 40).ToList();list.Add(50);foreach (var n in result){ Console.WriteLine(n); }
例子不是贴切,但是意思到了,此时的查询是立即执行.
2、有些查询比较耗时,比如对一个非常大的数据集进行操作或者通过Linq远程操作数据库操作数据时,这个时候的重复执行会严重影响性能.
延迟执行的实现原理
查询运算符通过返回装饰者sequence(decorator sequence)来支持延迟执行。
和传统的集合类型如array,linked list不同,一个装饰者sequence并没有自己用来存放元素的底层结构,而是包装了我们在运行时提供的另外一个sequence。此后当我们从装饰者sequence中请求数据时,它就会转而从包装的sequence中请求数据。
比如调用Where会创建一个装饰者sequence,其中保存了输入sequence的引用、lambda表达式还有其他提供的参数。下面的查询对应的装饰者sequence如图所示:
IEnumerable lessThanTen = new int[] { 5, 12, 3 }.Where(n => n < 10);
当我们遍历lessThanTen时,实际上我们是在通过Where装饰者从Array中查找数据。
而查询运算符链接创建了一个多层的装饰者,每个查询运算符都会实例化一个装饰者来包装前一个sequence,比如下面的query和对应的多层装饰者sequence:
IEnumerable query = new int[] { 5, 12, 3 } .Where(n => n < 10) .OrderBy(n => n) .Select(n => n * 10);
在我们遍历query时,我们其实是在通过一个装饰者链来查询最初的array。
需要注意的是,如果在上面的查询后面加上一个转换运算符如ToList,那么query会被立即执行,这样,单个list就会取代上面的整个对象模型。