众所周知,如果一个类可以被枚举,那么这个类必须要实现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中返回的那些匿名枚举类对你来说应该就没什么问题了~~~