前回作成したGetEnumuratorメソッドで反復しブロックがどのようなコードを生成したかReflector*1で見てみましょう。
public IEnumerator<int> GetEnumerator() { <GetEnumerator>d__0 d__ = new <GetEnumerator>d__0(0); d__.<>4__this = this; return d__; }
ではこの
[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/