你的位置:首页 > ASP.net教程

[ASP.net教程]Linq专题之提高编码效率—— 第三篇 你需要知道的枚举类


 

   众所周知,如果一个类可以被枚举,那么这个类必须要实现IEnumerable接口,而恰恰我们所有的linq都是一个继承自IEnumerable接口的匿名类,

那么问题就来了,IEnumerable使了何等神通让这些集合类型可以被自由的枚举???

 

一: 探索IEnumerable

  首先我们看看此接口都定义了些什么东西,如ILSpy所示:

 

从这个接口中,好像也仅仅有一个IEnumerator接口类型的方法之外,并没有可以挖掘的东西,这时候大家就应该好奇了,foreach既然可以枚举Collection,

那foreach背后的机制和GetEnumerator()有什么关系呢???说干就干,我们写一个demo,用ILDasm看看背后的IL应该就清楚了。

 

C#代码:

   static void Main(string[] args)    {      List<Action> list = new List<Action>();      foreach (var item in list)      {        Console.WriteLine();      }    }

 

IL代码:

.method private hidebysig static void Main(string[] args) cil managed{ .entrypoint // Code size    60 (0x3c) .maxstack 1 .locals init ([0] class [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action> list,      [1] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class [mscorlib]System.Action> V_1,      [2] class [mscorlib]System.Action item) IL_0000: nop IL_0001: newobj   instance void class [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action>::.ctor() IL_0006: stloc.0 IL_0007: nop IL_0008: ldloc.0 IL_0009: callvirt  instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action>::GetEnumerator() IL_000e: stloc.1 .try {  IL_000f: br.s    IL_0021  IL_0011: ldloca.s  V_1  IL_0013: call    instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class [mscorlib]System.Action>::get_Current()  IL_0018: stloc.2  IL_0019: nop  IL_001a: call    void [mscorlib]System.Console::WriteLine()  IL_001f: nop  IL_0020: nop  IL_0021: ldloca.s  V_1  IL_0023: call    instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class [mscorlib]System.Action>::MoveNext()  IL_0028: brtrue.s  IL_0011  IL_002a: leave.s  IL_003b } // end .try finally {  IL_002c: ldloca.s  V_1  IL_002e: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class [mscorlib]System.Action>  IL_0034: callvirt  instance void [mscorlib]System.IDisposable::Dispose()  IL_0039: nop  IL_003a: endfinally } // end handler IL_003b: ret} // end of method Program::Main

 

从IL中标红的字体来看,原来所谓的foreach,本质上调用的是list的GetEnumerator()方法来返回一个Enumerator枚举类型,然后在while循环中通过

current获取当前值,然后用MoveNext()获取下一个值,以此类推,如果把IL还原一下,大概就是下面这样:

      var enumerator = list.GetEnumerator();      try      {        while (enumerator.MoveNext())        {          Console.WriteLine(enumerator.Current);        }      }      finally      {        enumerator.Dispose();      }

 

这个时候你是不是有种强烈的欲望来探索GetEnumerator()到底干了什么,以及MoveNext()在其中扮演了什么角色??? 下面我们用ILSpy看看List下面

所谓的Enumerator类型。。。

 

 1   [Serializable] 2     public struct Enumerator : IEnumerator<T>, IDisposable, IEnumerator 3     { 4       private List<T> list; 5       private int index; 6       private int version; 7       private T current; 8       [__DynamicallyInvokable] 9       public T Current10       {11         [__DynamicallyInvokable]12         get13         {14           return this.current;15         }16       }17       [__DynamicallyInvokable]18       object IEnumerator.Current19       {20         [__DynamicallyInvokable]21         get22         {23           if (this.index == 0 || this.index == this.list._size + 1)24           {25             ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);26           }27           return this.Current;28         }29       }30       internal Enumerator(List<T> list)31       {32         this.list = list;33         this.index = 0;34         this.version = list._version;35         this.current = default(T);36       }37       [__DynamicallyInvokable]38       public void Dispose()39       {40       }41       [__DynamicallyInvokable]42       public bool MoveNext()43       {44         List<T> list = this.list;45         if (this.version == list._version && this.index < list._size)46         {47           this.current = list._items[this.index];48           this.index++;49           return true;50         }51         return this.MoveNextRare();52       }53       private bool MoveNextRare()54       {55         if (this.version != this.list._version)56         {57           ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);58         }59         this.index = this.list._size + 1;60         this.current = default(T);61         return false;62       }63       [__DynamicallyInvokable]64       void IEnumerator.Reset()65       {66         if (this.version != this.list._version)67         {68           ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);69         }70         this.index = 0;71         this.current = default(T);72       }73     }

 

通过查看所谓的Enumerator类的定义,尤其是标红的地方,可能会让你顿然醒悟,其实所谓的枚举类,仅仅是一个枚举集合的包装类,比如这里的List,

然后枚举类通过index++ 这种手段来逐一获取List中的元素,仅此而已。

 

二:yield关键词

  当大家明白了所谓的枚举类之后,是不是想到了一个怪异的yield词法,这个掉毛竟然还可以被枚举,就比如下面这样代码:

 1 class Program 2 { 3   static void Main(string[] args) 4   { 5     foreach (var item in Person.Run()) 6     { 7       Console.WriteLine(item); 8     } 9 10   }11 }12 13 class Person14 {15   public static IEnumerable<int> Run()16   {17     List<int> list = new List<int>();18 19     foreach (var item in list)20     {21       yield return item;22     }23   }24 }

 

那究竟yield干了什么呢? 而且能够让它人可以一探究竟??? 我们用ILDasm看一下。

 

仔细查看上面的代码,原来所谓的yield会给你生成一个枚举类,而这个枚举类和刚才List中的Enumerator枚举类又无比的一样,如果你理解了显示

的枚举类Enumerator,我想这个匿名的枚举类Enumerator应该就非常简单了。

 

好了,大概就说这么多了,有了这个基础,我相信linq中返回的那些匿名枚举类对你来说应该就没什么问题了~~~