@ledsun blog

無味の味は佳境に入らざればすなわち知れず

反復子ブロック

前回作成したGetEnumuratorメソッドで反復しブロックがどのようなコードを生成したかReflector*1で見てみましょう。

public IEnumerator<int> GetEnumerator()
{
    <GetEnumerator>d__0 d__ = new <GetEnumerator>d__0(0);
    d__.<>4__this = this;
    return d__;
}

d__0というクラスを返すメソッドとして実装されています。
d__0クラスは、foreach文で呼び出せるようにMoveNextメソッドを実装したIEnumerator型のクラスになっています。
ではこのd__0というクラスはどのように定義されているでしょうか?

[CompilerGenerated]
private sealed class <GetEnumerator>d__0 : IEnumerator<int>, IEnumerator, IDisposable
{
    // Fields
    private int <>1__state;
    private int <>2__current;
    public Simplest <>4__this;

    // Methods
    [DebuggerHidden]
    public <GetEnumerator>d__0(int <>1__state)
    {
        this.<>1__state = <>1__state;
    }

    private bool MoveNext()
    {
        switch (this.<>1__state)
        {
            case 0:
                this.<>1__state = -1;
                this.<>2__current = 0;
                this.<>1__state = 1;
                return true;

            case 1:
                this.<>1__state = -1;
                break;
        }
        return false;
    }

    [DebuggerHidden]
    void IEnumerator.Reset()
    {
        throw new NotSupportedException();
    }

    void IDisposable.Dispose()
    {
    }

    // Properties
    int IEnumerator<int>.Current
    {
        [DebuggerHidden]
        get
        {
            return this.<>2__current;
        }
    }

    object IEnumerator.Current
    {
        [DebuggerHidden]
        get
        {
            return this.<>2__current;
        }
    }
}

ポイントはMoveNextメソッドです。
yield returnで与えられた回数に応じて状態を持ち、呼び出される度にthis.<>2__currentの値を変更し、プロパティCurrentの返す値を変更しています。例えば、yield returnをする回数を以下のように増やせば、MoveNext内での状態も増えます。

public IEnumerator<int> GetEnumerator()
{
    yield return 0;
    yield return 1;
}

private bool MoveNext()
{
    switch (this.<>1__state)
    {
        case 0:
            this.<>1__state = -1;
            this.<>2__current = 0;
            this.<>1__state = 1;
            return true;

        case 1:
            this.<>1__state = -1;
            this.<>2__current = 1;
            this.<>1__state = 2;
            return true;

        case 2:
            this.<>1__state = -1;
            break;
    }
    return false;
}

ループ内からyield returnを呼び出した場合も適切な状態を生成します。

public IEnumerator<int> GetEnumerator()
{
    for (int i = 0; i < 10; i++)
        yield return i;
}

private bool MoveNext()
{
    switch (this.<>1__state)
    {
        case 0:
            this.<>1__state = -1;
            this.<i>5__1 = 0;
            break;

        case 1:
            this.<>1__state = -1;
            this.<i>5__1++;
            break;

        default:
            goto Label_005B;
    }
    if (this.<i>5__1 < 10)
    {
        this.<>2__current = this.<i>5__1;
        this.<>1__state = 1;
        return true;
    }
Label_005B:
    return false;
}

また親クラスへの参照を持っているため、親クラスのフィールドの状態に依存した値の変更も可能です。

bool flag;
public IEnumerator<int> GetEnumerator()
{
    if (flag)
       yield return 0;
}

private bool MoveNext()
{
    switch (this.<>1__state)
    {
        case 0:
            this.<>1__state = -1;
            if (!this.<>4__this.flag)
            {
                break;
            }
            this.<>2__current = 0;
            this.<>1__state = 1;
            return true;

        case 1:
            this.<>1__state = -1;
            break;
    }
    return false;
}

このようにコンパイラが凄く賢くIEnumerator型のクラスを実装してくれるためGetEnumeratorメソッドの実装が簡単になっています。型付けの強い言語でありながら、コンパイラの助けによって比較的柔軟なコードが記述できるのがC#の良いところですね。

*1:http://www.moongift.jp/2007/10/feflector_for_net/