counter
counter

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

签名:

[csharp] view plaincopy
  1. public static ILookup<TKey, TSource> ToLookup<TSource, TKey>(  
  2.     this IEnumerable<TSource> source,  
  3.     Func<TSource, TKey> keySelector  
  4. )  




解说:

与GroupBy功能差不多,都会创建类字典集合,区别在于:
GroupBy是延迟加载,所以即使使用GroupBy得到结果集合,若原操作目标集合发生改变,那结果集合的元素也会发生相应的改变。
创建一个ILookup集合,此集合不像Dictionary,其元素是不可改变的。
非延迟执行。

示例:

[csharp] view plaincopy
  1. public sealed class Product  
  2. {  
  3.     public int Id { getset; }  
  4.     public string Category { getset; }  
  5.     public double Value { getset; }  
  6.   
  7.     public override string ToString()  
  8.     {  
  9.         return string.Format("[{0}: {1} - {2}]", Id, Category, Value);  
  10.     }  
  11. }  
  12.   
  13. public static void Test()  
  14. {  
  15.     var products = new List<Product>   
  16.                 {   
  17.                     new Product {Id = 1, Category = "Electronics", Value = 15.0},   
  18.                     new Product {Id = 2, Category = "Groceries", Value = 40.0},   
  19.                     new Product {Id = 3, Category = "Garden", Value = 210.3},   
  20.                     new Product {Id = 4, Category = "Pets", Value = 2.1},   
  21.                     new Product {Id = 5, Category = "Electronics", Value = 19.95},   
  22.                     new Product {Id = 6, Category = "Pets", Value = 21.25},   
  23.                     new Product {Id = 7, Category = "Pets", Value = 5.50},   
  24.                     new Product {Id = 8, Category = "Garden", Value = 13.0},   
  25.                     new Product {Id = 9, Category = "Automotive", Value = 10.0},   
  26.                     new Product {Id = 10, Category = "Electronics", Value = 250.0},   
  27.                 };  
  28.   
  29.     //若使用GroupBy,则所有Garden产品最后不会被打印出来  
  30.     //若使用ToLookup,则所有Garden产品最后还是会被打印出来  
  31.     var groups = products.ToLookup(pr => pr.Category);  
  32.   
  33.     //删除所有属于Garden的产品  
  34.     products.RemoveAll(pr => pr.Category == "Garden");  
  35.   
  36.     //打印结果  
  37.     foreach (var group in groups)  
  38.     {  
  39.         Console.WriteLine(group.Key);  
  40.         foreach (var item in group)  
  41.         {  
  42.             Console.WriteLine("\t" + item);  
  43.         }  
  44.     }  
  45. }  





二、Zip

签名:

[csharp] view plaincopy
  1. public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(  
  2.     this IEnumerable<TFirst> first,  
  3.     IEnumerable<TSecond> second,  
  4.     Func<TFirst, TSecond, TResult> resultSelector  
  5. )  




解说:

合并两个序列。
结果序列的元素个数与第一个序列的元素个数相同。
延迟执行。

示例:

[csharp] view plaincopy
  1. int[] numbers = { 1, 2, 3, 4 };  
  2. string[] words = { "one""two""three" };  
  3.   
  4. var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);  
  5.   
  6. foreach (var item in numbersAndWords)  
  7.     Console.WriteLine(item);  
  8.   
  9. // This code produces the following output:  
  10.   
  11. // 1 one  
  12. // 2 two  
  13. // 3 three  





三、Repeat

签名:

[csharp] view plaincopy
  1. public static IEnumerable<TResult> Repeat<TResult>(  
  2.     TResult element,  
  3.     int count  
  4. )  




解说:

生成包含一个重复值的序列
注意非扩展方法。
延迟执行。

示例:

 

[csharp] view plaincopy
  1. IEnumerable<string> strings = Enumerable.Repeat("I like programming.", 15);  
  2.   
  3. foreach (String str in strings)  
  4. {  
  5.     Console.WriteLine(str);  
  6. }  
  7.   
  8. /* 
  9.  This code produces the following output: 
  10.  
  11.  I like programming. 
  12.  I like programming. 
  13.  I like programming. 
  14.  I like programming. 
  15.  I like programming. 
  16.  I like programming. 
  17.  I like programming. 
  18.  I like programming. 
  19.  I like programming. 
  20.  I like programming. 
  21.  I like programming. 
  22.  I like programming. 
  23.  I like programming. 
  24.  I like programming. 
  25.  I like programming. 
  26. */  


 

四、Range

签名:

[csharp] view plaincopy
  1. public static IEnumerable<int> Range(  
  2.     int start,  
  3.     int count  
  4. )  




解说:

生成指定范围内的整数的序列。
延迟执行。

示例:

[csharp] view plaincopy
  1. IEnumerable<int> squares = Enumerable.Range(1, 5);  
  2.   
  3. foreach (var num in squares)  
  4. {  
  5.     Console.WriteLine(num);  
  6. }  
  7.   
  8. /* 
  9.  This code produces the following output: 
  10.  
  11.  1 
  12.  2 
  13.  3 
  14.  4 
  15.  5 
  16. */  





五、OfType

签名:

[csharp] view plaincopy
  1. public static IEnumerable<TResult> OfType<TResult>(  
  2.     this IEnumerable source  
  3. )  




解说:

根据指定类型筛选 IEnumerable 的元素。
延迟执行。

示例:

[csharp] view plaincopy
  1. System.Collections.ArrayList fruits = new System.Collections.ArrayList(4);  
  2. fruits.Add("Mango");  
  3. fruits.Add("Orange");  
  4. fruits.Add("Apple");  
  5. fruits.Add(3.0);  
  6. fruits.Add(4.0);  
  7. fruits.Add(5);  
  8. fruits.Add("6.0");  
  9.   
  10. // Apply OfType() to the ArrayList.  
  11. IEnumerable<double > query1 = fruits.OfType<double>();  
  12.   
  13. Console.WriteLine("Elements of type 'double' are:");  
  14. foreach (var element in query1)  
  15. {  
  16.     Console.WriteLine(element);  
  17. }  
  18.   
  19. // This code produces the following output:  
  20. //  
  21. // Elements of type 'double' are:  
  22. // 3  
  23. // 4  






六:Join

签名:

[csharp] view plaincopy
  1. public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(  
  2.     this IEnumerable<TOuter> outer,  
  3.     IEnumerable<TInner> inner,  
  4.     Func<TOuter, TKey> outerKeySelector,  
  5.     Func<TInner, TKey> innerKeySelector,  
  6.     Func<TOuter, TInner, TResult> resultSelector  
  7. )  




解说:

延迟执行。
用法与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

示例:

[csharp] view plaincopy
    1. class Person  
    2. {  
    3.     public string Name { getset; }  
    4. }  
    5.   
    6. class Pet  
    7. {  
    8.     public string Name { getset; }  
    9.     public Person Owner { getset; }  
    10. }  
    11.   
    12. public static void JoinEx1()  
    13. {  
    14.     Person magnus = new Person { Name = "Hedlund, Magnus" };  
    15.     Person terry = new Person { Name = "Adams, Terry" };  
    16.     Person charlotte = new Person { Name = "Weiss, Charlotte" };  
    17.   
    18.     Pet barley = new Pet { Name = "Barley", Owner = terry };  
    19.     Pet boots = new Pet { Name = "Boots", Owner = terry };  
    20.     Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };  
    21.     Pet daisy = new Pet { Name = "Daisy", Owner = magnus };  
    22.   
    23.     List<Person> people = new List<Person> { magnus, terry, charlotte };  
    24.     List<Pet> pets = new List<Pet> { barley, boots, whiskers, daisy };  
    25.   
    26.     // Create a list of Person-Pet pairs where   
    27.     // each element is an anonymous type that contains a  
    28.     // Pet's name and the name of the Person that owns the Pet.  
    29.     var query =  
    30.         people.Join(pets,  
    31.                     person => person,  
    32.                     pet => pet.Owner,  
    33.                     (person, pet) =>  
    34.                         new { OwnerName = person.Name, Pet = pet.Name });  
    35.   
    36.     foreach (var obj in query)  
    37.     {  
    38.         Console.WriteLine(  
    39.             "{0} - {1}",  
    40.             obj.OwnerName,  
    41.             obj.Pet);  
    42.     }  
    43. }  
    44.   
    45. /* 
    46.  This code produces the following output: 
    47.  
    48.  Hedlund, Magnus - Daisy 
    49.  Adams, Terry - Barley 
    50.  Adams, Terry - Boots 
    51.  Weiss, Charlotte - Whiskers 
    52. */  

 

posted @ 2012-08-02 15:35  bfy  阅读(643)  评论(0编辑  收藏  举报