LINQ - 延迟执行机制分析及常用的Enumerable 类中的几个Linq方法
转载:http://hi.baidu.com/quark282/blog/item/ea530af0e0b989d7f2d385bc.html
编译器会将 LINQ 表达式编译成委托,然后作为参数传递给相应的扩展方法。
扩展方法(如 Enumerable.Select)只是创建了一个实现了 IEnumerable<T> 接口的对象,该对象持有委托和数据源对象的引用。
在没有调用 IEnumerable<T>.MoveNext() 之前,委托并不会被执行,自然这个委托中的 "外部变量" 不会被修改(参考《C# 2.0 - Anonymous Methods》)。
当我们对这个 "IEnumerable<T> 对象" 进行操作时,必然会调用 MoveNext(),从而触发委托,进而影响到 "外部变量"。
你或许还有点迷糊,没关系,看所谓经典的例子。
代码1
var num = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var i = 0; var q = from n in num select ++i; foreach (var n in q) { Console.WriteLine("n = {0}; i = {1}", n, i); }
输出
n = 1; i = 1
n = 2; i = 2
n = 3; i = 3
n = 4; i = 4
n = 5; i = 5
n = 6; i = 6
n = 7; i = 7
n = 8; i = 8
n = 9; i = 9
代码2
var num = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var i = 0; var q = (from n in num select ++i).ToList(); foreach (var n in q) { Console.WriteLine("n = {0}; i = {1}", n, i); }
输出
n = 1; i = 9
n = 2; i = 9
n = 3; i = 9
n = 4; i = 9
n = 5; i = 9
n = 6; i = 9
n = 7; i = 9
n = 8; i = 9
n = 9; i = 9
为什么加了 ToList() 后,会导致结果发生如此大的变化呢?
代码1反编译结果
int[] num = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int i = 0; IEnumerable<int> q = num.Select<int, int>(delegate (int n) { return ++i; }); foreach (int n in q) { Console.WriteLine("n = {0}; i = {1}", n, i); }
对照这个反编译结果,我们很容易理解上面演示的输出结果。当扩展方法 Enumerable.Select() 执行完成后,返回了一个实现了 IEnumerable<int> 接口的对象,该对象内部持有委托的引用,而这个委托又持有外部变量 i 的引用。也就是说,这时候大家都牵了根绳,委托没被执行,也没谁去改变 i 的值 (i = 0)。而一旦开始执行 foreach 代码块,每次循环都会调用 IEnumerable<T>.MoveNext() 方法,该方法内部开始调用委托,委托每次执行都会导致 i 的值都被累加一次(++i),故输出结果是 1~9。
代码2反编译结果
int[] num = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int i = 0; List<int> q = num.Select<int, int>(delegate (int n) { return ++i; }).ToList<int>(); foreach (int n in q) { Console.WriteLine("n = {0}; i = {1}", n, i); }
要想看明白,我们还得搞清楚 ToList 这个扩展方法做了些什么。
public static class Enumerable { public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) { return new List<TSource>(source); } } public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable { public List(IEnumerable<T> collection) { ICollection<T> is2 = collection as ICollection<T>; if (is2 != null) { int count = is2.Count; this._items = new T[count]; is2.CopyTo(this._items, 0); this._size = count; } else { this._size = 0; this._items = new T[4]; using (IEnumerator<T> enumerator = collection.GetEnumerator()) { while (enumerator.MoveNext()) { this.Add(enumerator.Current); } } } } }
由于扩展方法 Enumerable.Select() 返回的对象仅实现了 IEnumerable<T>,因此导致 List<T> 构造方法中 else 语句块被执行。在我们开始 foreach 循环之前,IEnumerable<T>.MoveNext() 和委托就已经被 List<T>.ctor 遍历执行了一遍,那么也就是说调用 ToList<TSource>() 扩展方法以后,i 的值已经被累加到 9 了。再接下来进行 foreach 循环就有误导的嫌疑了,q 作为 List<int> 存储了 1 ~ 9,但没有任何代码再去修改变量 i (i = 9),循环的结果不过是 i 被显示了 q.Count 次而已,输出结果要不都是 9 那才见鬼了呢。
看看别人是怎么说的。
------------以下文字摘自 Furture C# - 《Linq 入门系列 select篇》 评论部分---------------
小结:
Q:通过上面几个例子,我们该如何理解LINQ的查询何时执行呢?
A:LINQ的查询执行遵循以下原则:
1、一般情况下(除了下面第三条说的情况),LINQ都是延迟执行,原因:以DLINQ为例,越晚被执行,对业务逻辑的理解就越清晰,DLINQ查询对数据库的请求压力越小。编译器对LINQ查询优化可作的事情越多。
2、由于是延迟执行,也就是调用的时候才去执行。这样调用一次就被执行一次,这样就具备了重复执行的功能,参看之前的几个重复执行的例子。而这个重复执行是不需要再此书写一边查询语句的。
3、如果查询中我们对查询结果使用了 ToArray、ToList、ToDictionary 这些转换成集合的扩展方法。使用这时候出来的对象是一个独立的集合数组,而不是LINQ查询,所以这时候不会出现多次查询,而只是一次查询。
即:var q = from n in numbers select ++i ; 这样一条语句我们可以认为它记录的不是等号右边的结果,而是记录的等号右边的表达式。
而 var q = (from n in numbers select ++i).ToDictionary(k => k); 这样一条语句我们记录的是等号右边的计算结果,而不是表达式。
-------摘录结束-------------------
上面这段摘录中作者的说法基本没啥问题,但多少有些误导的嫌疑。作者并没有给出具体导致延迟执行的原因分析,仅仅通过输出结果来判断,似乎不够深入,也缺乏可靠的依据。而 "以DLINQ为例,越晚被执行,对业务逻辑的理解就越清晰,DLINQ查询对数据库的请求压力越小。编译器对LINQ查询优化可作的事情越多。" 让我觉得有点怪怪的…… 编译器将表达式 "拆解" 成一个或多个 Lambda Expression,动态组合到一起,传递到最终的 DataQuery:IQueryable<T> 对象中。只有我们执行相关操作,触发 IEnumerable<T>.GetEnumerator() 方法时才会执行 ADO.NET 操作,这就是所谓延时执行的过程。
转载: http://blog.csdn.net/iaki2008/article/details/6865815
一、ToLookup
签名:
- public static ILookup<TKey, TSource> ToLookup<TSource, TKey>(
- this IEnumerable<TSource> source,
- Func<TSource, TKey> keySelector
- )
解说:
与GroupBy功能差不多,都会创建类字典集合,区别在于:
GroupBy是延迟加载,所以即使使用GroupBy得到结果集合,若原操作目标集合发生改变,那结果集合的元素也会发生相应的改变。
创建一个ILookup集合,此集合不像Dictionary,其元素是不可改变的。
非延迟执行。
示例:
- public sealed class Product
- {
- public int Id { get; set; }
- public string Category { get; set; }
- public double Value { get; set; }
- public override string ToString()
- {
- return string.Format("[{0}: {1} - {2}]", Id, Category, Value);
- }
- }
- public static void Test()
- {
- var products = new List<Product>
- {
- new Product {Id = 1, Category = "Electronics", Value = 15.0},
- new Product {Id = 2, Category = "Groceries", Value = 40.0},
- new Product {Id = 3, Category = "Garden", Value = 210.3},
- new Product {Id = 4, Category = "Pets", Value = 2.1},
- new Product {Id = 5, Category = "Electronics", Value = 19.95},
- new Product {Id = 6, Category = "Pets", Value = 21.25},
- new Product {Id = 7, Category = "Pets", Value = 5.50},
- new Product {Id = 8, Category = "Garden", Value = 13.0},
- new Product {Id = 9, Category = "Automotive", Value = 10.0},
- new Product {Id = 10, Category = "Electronics", Value = 250.0},
- };
- //若使用GroupBy,则所有Garden产品最后不会被打印出来
- //若使用ToLookup,则所有Garden产品最后还是会被打印出来
- var groups = products.ToLookup(pr => pr.Category);
- //删除所有属于Garden的产品
- products.RemoveAll(pr => pr.Category == "Garden");
- //打印结果
- foreach (var group in groups)
- {
- Console.WriteLine(group.Key);
- foreach (var item in group)
- {
- Console.WriteLine("\t" + item);
- }
- }
- }
二、Zip
签名:
- public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
- this IEnumerable<TFirst> first,
- IEnumerable<TSecond> second,
- Func<TFirst, TSecond, TResult> resultSelector
- )
解说:
合并两个序列。
结果序列的元素个数与第一个序列的元素个数相同。
延迟执行。
示例:
- int[] numbers = { 1, 2, 3, 4 };
- string[] words = { "one", "two", "three" };
- var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);
- foreach (var item in numbersAndWords)
- Console.WriteLine(item);
- // This code produces the following output:
- // 1 one
- // 2 two
- // 3 three
三、Repeat
签名:
- public static IEnumerable<TResult> Repeat<TResult>(
- TResult element,
- int count
- )
解说:
生成包含一个重复值的序列
注意非扩展方法。
延迟执行。
示例:
- IEnumerable<string> strings = Enumerable.Repeat("I like programming.", 15);
- foreach (String str in strings)
- {
- Console.WriteLine(str);
- }
- /*
- This code produces the following output:
- I like programming.
- I like programming.
- I like programming.
- I like programming.
- I like programming.
- I like programming.
- I like programming.
- I like programming.
- I like programming.
- I like programming.
- I like programming.
- I like programming.
- I like programming.
- I like programming.
- I like programming.
- */
四、Range
签名:
- public static IEnumerable<int> Range(
- int start,
- int count
- )
解说:
生成指定范围内的整数的序列。
延迟执行。
示例:
- IEnumerable<int> squares = Enumerable.Range(1, 5);
- foreach (var num in squares)
- {
- Console.WriteLine(num);
- }
- /*
- This code produces the following output:
- 1
- 2
- 3
- 4
- 5
- */
五、OfType
签名:
- public static IEnumerable<TResult> OfType<TResult>(
- this IEnumerable source
- )
解说:
根据指定类型筛选 IEnumerable 的元素。
延迟执行。
示例:
- System.Collections.ArrayList fruits = new System.Collections.ArrayList(4);
- fruits.Add("Mango");
- fruits.Add("Orange");
- fruits.Add("Apple");
- fruits.Add(3.0);
- fruits.Add(4.0);
- fruits.Add(5);
- fruits.Add("6.0");
- // Apply OfType() to the ArrayList.
- IEnumerable<double > query1 = fruits.OfType<double>();
- Console.WriteLine("Elements of type 'double' are:");
- foreach (var element in query1)
- {
- Console.WriteLine(element);
- }
- // This code produces the following output:
- //
- // Elements of type 'double' are:
- // 3
- // 4
六:Join
签名:
- public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(
- this IEnumerable<TOuter> outer,
- IEnumerable<TInner> inner,
- Func<TOuter, TKey> outerKeySelector,
- Func<TInner, TKey> innerKeySelector,
- Func<TOuter, TInner, TResult> resultSelector
- )
解说:
延迟执行。
用法与SQL中的JOIN用法相似,参考以下的SQL:
USE pubs
SELECT a.au_fname, a.au_lname, p.pub_name
FROM authors a LEFT OUTER JOIN publishers p
ON a.city = p.city
示例:
- class Person
- {
- public string Name { get; set; }
- }
- class Pet
- {
- public string Name { get; set; }
- public Person Owner { get; set; }
- }
- public static void JoinEx1()
- {
- Person magnus = new Person { Name = "Hedlund, Magnus" };
- Person terry = new Person { Name = "Adams, Terry" };
- Person charlotte = new Person { Name = "Weiss, Charlotte" };
- Pet barley = new Pet { Name = "Barley", Owner = terry };
- Pet boots = new Pet { Name = "Boots", Owner = terry };
- Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
- Pet daisy = new Pet { Name = "Daisy", Owner = magnus };
- List<Person> people = new List<Person> { magnus, terry, charlotte };
- List<Pet> pets = new List<Pet> { barley, boots, whiskers, daisy };
- // Create a list of Person-Pet pairs where
- // each element is an anonymous type that contains a
- // Pet's name and the name of the Person that owns the Pet.
- var query =
- people.Join(pets,
- person => person,
- pet => pet.Owner,
- (person, pet) =>
- new { OwnerName = person.Name, Pet = pet.Name });
- foreach (var obj in query)
- {
- Console.WriteLine(
- "{0} - {1}",
- obj.OwnerName,
- obj.Pet);
- }
- }
- /*
- This code produces the following output:
- Hedlund, Magnus - Daisy
- Adams, Terry - Barley
- Adams, Terry - Boots
- Weiss, Charlotte - Whiskers
- */