眾所周知,如果一個類可以被枚舉,那麼這個類必須要實現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類型。。。

[Serializable]
public struct Enumerator : IEnumerator<T>, IDisposable, IEnumerator
{
private List<T> list;
private int index;
private int version;
private T current;
[__DynamicallyInvokable]
public T Current
{
[__DynamicallyInvokable]
get
{
return this.current;
}
}
[__DynamicallyInvokable]
object IEnumerator.Current
{
[__DynamicallyInvokable]
get
{
if (this.index == 0 || this.index == this.list._size + 1)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
}
return this.Current;
}
}
internal Enumerator(List<T> list)
{
this.list = list;
this.index = 0;
this.version = list._version;
this.current = default(T);
}
[__DynamicallyInvokable]
public void Dispose()
{
}
[__DynamicallyInvokable]
public bool MoveNext()
{
List<T> list = this.list;
if (this.version == list._version && this.index < list._size)
{
this.current = list._items[this.index];
this.index++;
return true;
}
return this.MoveNextRare();
}
private bool MoveNextRare()
{
if (this.version != this.list._version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
this.index = this.list._size + 1;
this.current = default(T);
return false;
}
[__DynamicallyInvokable]
void IEnumerator.Reset()
{
if (this.version != this.list._version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
this.index = 0;
this.current = default(T);
}
}
通過查看所謂的Enumerator類的定義,尤其是標紅的地方,可能會讓你頓然醒悟,其實所謂的枚舉類,僅僅是一個枚舉集合的包裝類,比如這裡的List,
然後枚舉類通過index++ 這種手段來逐一獲取List中的元素,僅此而已。
二:yield關鍵詞
當大家明白了所謂的枚舉類之後,是不是想到了一個怪異的yield詞法,這個掉毛竟然還可以被枚舉,就比如下面這樣代碼:
class Program
{
static void Main(string[] args)
{
foreach (var item in Person.Run())
{
Console.WriteLine(item);
}
}
}
class Person
{
public static IEnumerable<int> Run()
{
List<int> list = new List<int>();
foreach (var item in list)
{
yield return item;
}
}
}
那究竟yield干了什麼呢? 而且能夠讓它人可以一探究竟??? 我們用ILDasm看一下。

仔細查看上面的代碼,原來所謂的yield會給你生成一個枚舉類,而這個枚舉類和剛才List中的Enumerator枚舉類又無比的一樣,如果你理解了顯示
的枚舉類Enumerator,我想這個匿名的枚舉類Enumerator應該就非常簡單了。
好了,大概就說這麼多了,有了這個基礎,我相信linq中返回的那些匿名枚舉類對你來說應該就沒什麼問題了~~~