眾所周知,如果一個類可以被枚舉,那麼這個類必須要實現IEnumerable介面,而恰恰我們所有的linq都是一個繼承自IEnumerable介面的匿名類, 那麼問題就來了,IEnumerable使了何等神通讓這些集合類型可以被自由的枚舉??? 一: 探索IEnumerable 首先我們看看
眾所周知,如果一個類可以被枚舉,那麼這個類必須要實現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 Current 10 { 11 [__DynamicallyInvokable] 12 get 13 { 14 return this.current; 15 } 16 } 17 [__DynamicallyInvokable] 18 object IEnumerator.Current 19 { 20 [__DynamicallyInvokable] 21 get 22 { 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 Person 14 { 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中返回的那些匿名枚舉類對你來說應該就沒什麼問題了~~~