从IL认识关键字(二)
关键字
上一篇研究了foreach关键字,foreach关键字需要实现IEnumerable接口,实现GetEnumerator()方法。实现Enumerator()方法,很多时候用到yield关键字,今天我们研究一下yield关键字。
MSDN解释
- yield 关键字向编译器指示它所在的方法是迭代器块。
- 编译器生成一个类来实现迭代器块中表示的行为。
- 在迭代器块中, yield 关键字与 return 关键字结合使用,向枚举器对象提供值。
- yield 关键字也可与 break 结合使用,表示迭代结束。
其实MSDN已经解释清楚,今天我们主要研究第二点,C#会根据yield关键字生成一个类,看看这个类是怎样的。
C# IL Code
先从简单例子看,只有一个值的迭代器
public static IEnumerable Test() { yield return 10; }
对应的IL
.maxstack 2 .locals init ( [0] class Yield.Program/<Test>d__0 d__, [1] class [mscorlib]System.Collections.IEnumerable enumerable) L_0000: ldc.i4.s -2 L_0002: newobj instance void Yield.Program/<Test>d__0::.ctor(int32) L_0007: stloc.0 L_0008: ldloc.0 L_0009: stloc.1 L_000a: br.s L_000c L_000c: ldloc.1 L_000d: ret
<Test>d_0这个就是编译器生成的一个类,这段代码翻译成真正的运行代码如下
public static System.Collections.IEnumerable YieldOnly() { return new d__0(-2); }
public <Test>d__0(int <>1__state) { this.<>1__state = <>1__state; this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId; }
这个就是<Test>d_0的构造函数。那究竟d_0这个类是怎样的,由于篇幅有限,这里不全贴出来。有兴趣同学自己可以反编译一下,看看就清楚。它生成时一个密封类,基础IEnumerable,IEnumerator两个接口,说白了就是一个迭代器。
[CompilerGenerated] private sealed class <Test>d__0 : IEnumerable<object>, IEnumerable, IEnumerator<object>, IEnumerator, IDisposable { .... }
下面主要贴出两个方法解释。
private bool MoveNext() { switch (this.<>1__state) { case 0: this.<>1__state = -1; this.<>2__current = 10; this.<>1__state = 1; return true; case 1: this.<>1__state = -1; break; } return false; }
看到这里有人奇怪 _state = -2, 这里返回false,就不能遍历。上一篇我们说了foreach的原理,它是先调用GetEnumerator()获取迭代器对象,再遍历。
IEnumerator<object> IEnumerable<object>.GetEnumerator() { if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2)) { this.<>1__state = 0; return this; } return new Program.<Test>d__0(0); }
初始化放在这里,这里不知道为什么会判断一个是不是当前的线程,难道它还会跨线程调用。
循环的yield
public System.Collections.IEnumerable YieldCycle() { for (int i = 0; i < 10; i++) { yield return i; } }
对应MoveNext()
private bool MoveNext() { switch (this.<>1__state) { case 0: this.<>1__state = -1; this.<i>5__7 = 0; while (this.<i>5__7 < 10) {
//1.迭代 this.<>2__current = this.<i>5__7; this.<>1__state = 1; return true;
//2.叠加 Label_004B: this.<>1__state = -1; this.<i>5__7++; } break; case 1: goto Label_004B; } return false; }
循环语句基本分为两个模块,一个迭代的部分,一个是叠加的部分。对于有判断条件的yield return的,会将叠加部分拆分。
下一篇关键字
以上只是本人的理解与实践,如有错误不足之处希望理解包容,下一篇讨论using关键字