C#では2.0より、VBではVB11(VB2012)よりイテレータ構文が導入されています。 これはIEnumerator<T>(またはIEnumerable)の実装をより簡単に記述できるものです。 C#ではyieldキーワード、VBではYieldステートメントを用いることでイテレータを実装することができます。

§1 イテレータ (反復子)

イテレータがどのようなものか具体的な例を見てみます。

イテレータ
using System;
using System.Collections.Generic;

class Sample {
  // イテレータメソッド
  static IEnumerable<string> Enumerate()
  {
    // yield returnでイテレータが列挙する値を返す
    yield return "foo";
    yield return "bar";
    yield return "baz";
  }

  static void Main()
  {
    // 通常のIEnumerable<T>と同様に列挙することができる
    foreach (var e in Enumerate()) {
      Console.WriteLine(e);
    }
  }
}
実行結果
foo
bar
baz

このようにイテレータ構文を使うとIEnumerable・IEnumeratorを実装しなくても列挙ができるようになっています。 C#ではyield return 、VBではYield とすることで列挙される値を返すことができます。 VBでは、イテレータとなるメソッドにIterator修飾子を付ける必要があります。


もうひとつ違いと相違点を明らかにするための別の例を挙げます。 イテレータとIEnumerable・IEnumeratorのそれぞれで同じ要素を列挙するクラスを実装すると次のようになります。 (ここでは記述をシンプルにするためにIEnumerable<T>ではなく非ジェネリックなIEnumerableを実装しています)

イテレータ
using System;
using System.Collections;

class IntIterator : IEnumerable {
  public IEnumerator GetEnumerator()
  {











    yield return "foo";
    yield return "bar";
    yield return "baz";




  }















}

class Sample {
  static void Main()
  {
    foreach (var e in new IntIterator()) {
      Console.WriteLine(e);
    }
  }
}
IEnumerable・IEnumerator
using System;
using System.Collections;

class IntEnumerable : IEnumerable {
  public IEnumerator GetEnumerator()
  {
    return new IntEnumerator();
  }
}

class IntEnumerator : IEnumerator {
  private int index = -1;

  public object Current {
    get {
      switch (index) {
        case 0: return "foo";
        case 1: return "bar";
        case 2: return "baz";
      }

      throw new InvalidOperationException();
    }
  }

  public bool MoveNext()
  {
    index++;

    if (index <= 2)
      return true;
    else
      return false;
  }

  public void Reset()
  {
    throw new NotSupportedException();
  }
}

class Sample {
  static void Main()
  {
    foreach (var e in new IntEnumerable()) {
      Console.WriteLine(e);
    }
  }
}
実行結果
foo
bar
baz

イテレータを使ったコードでは、コンパイル時にCurrentプロパティやMoveNextメソッドなどIEnumerator(またはIEnumerator<T>)に必要なメンバが自動的に実装されます。



§2 遅延実行

イテレータの動作はIEnumerable<T>を実装した場合と変わりないように見えますが、異なる動作となる部分もあります。

using System;
using System.Collections.Generic;

class Sample {
  // 0から4の値を生成するメソッド
  static IEnumerable<int> GenerateNumbers()
  {
    yield return 0;
    yield return 1;
    yield return 2;
    yield return 3;
    yield return 4;
  }

  // 列挙される値を倍にして返すメソッド
  static IEnumerable<int> Double(IEnumerable<int> list)
  {
    foreach (var e in list) {
      yield return e * 2;
    }
  }

  static void Main()
  {
    foreach (var e in Double(GenerateNumbers())) {
      Console.WriteLine(e);
    }
  }
}
実行結果
0
2
4
6
8

この結果を見ると普通にListなどを列挙する場合と変わらないように見えますが、yieldは結果が生成されるタイミングが違います。 GenerateNumbersメソッドを次のように変えてyieldが実行されるタイミングを調べてみると違いが明らかになります。

using System;
using System.Collections.Generic;

class Sample {
  // 0から4の値を生成するメソッド
  static IEnumerable<int> GenerateNumbers()
  {
    Console.WriteLine("yield 0"); yield return 0;
    Console.WriteLine("yield 1"); yield return 1;
    Console.WriteLine("yield 2"); yield return 2;
    Console.WriteLine("yield 3"); yield return 3;
    Console.WriteLine("yield 4"); yield return 4;
  }

  // 列挙される値を倍にして返すメソッド
  static IEnumerable<int> Double(IEnumerable<int> list)
  {
    foreach (var e in list) {
      yield return e * 2;
    }
  }

  static void Main()
  {
    foreach (var e in Double(GenerateNumbers())) {
      Console.WriteLine(e);
    }
  }
}
実行結果
yield 0
0
yield 1
2
yield 2
4
yield 3
6
yield 4
8

この動作は遅延実行と呼ばれるものです。 通常のメソッドとは異なり、イテレータとなるメソッドでは呼び出された時点でIEnumerableが返されるものの、イテレータメソッド内のyield文は実際に値が列挙されるまでは呼び出されません。

§3 イテレータ実装時の注意点

イテレータ構文は、戻り値の型がIEnumerable(IEnumerable<T>)もしくはIEnumerator(IEnumerator<T>)であるメソッドまたはプロパティで記述することができます。

ただし、匿名メソッドでは使用できない、例外処理に制限があるなど、イテレータ構文特有の注意点があります。 詳細は反復子 (C# プログラミング ガイド)を参照してください。