从IL认识关键字(二)

关键字

     上一篇研究了foreach关键字,foreach关键字需要实现IEnumerable接口,实现GetEnumerator()方法。实现Enumerator()方法,很多时候用到yield关键字,今天我们研究一下yield关键字。

MSDN解释

  1.  yield 关键字向编译器指示它所在的方法是迭代器块。
  2. 编译器生成一个类来实现迭代器块中表示的行为。
  3. 在迭代器块中, yield 关键字与 return 关键字结合使用,向枚举器对象提供值。
  4. 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关键字

原文地址:https://www.cnblogs.com/WilsonPan/p/2900441.html