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

イテレータ (反復子)

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

イテレータ
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);
    }
  }
}
イテレータ
Imports System
Imports System.Collections.Generic

Class Sample
  ' イテレータメソッド
  Shared Iterator Function Enumerate() As IEnumerable(Of String)
    ' Yieldでイテレータが列挙する値を返す
    Yield "foo"
    Yield "bar"
    Yield "baz"
  End Function

  Shared Sub Main()
    ' 通常のIEnumerable(Of Integer)と同様に列挙することができる
    For Each e As String In Enumerate()
      Console.WriteLine(e)
    Next
  End Sub
End Class
実行結果
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);
    }
  }
}

イテレータ
Imports System
Imports System.Collections

Class IntIterator
  Implements IEnumerable

  Public Iterator Function GetEnumerator() As IEnumerator _
  Implements IEnumerable.GetEnumerator













    Yield "foo"
    Yield "bar"
    Yield "baz"




  End Function
















End Class

Class Sample
  Shared Sub Main()
    For Each e As String In New IntIterator()
      Console.WriteLine(e)
    Next
  End Sub
End Class
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);
    }
  }
}
IEnumerable・IEnumerator
Imports System
Imports System.Collections

Class IntEnumerable
  Implements IEnumerable

  Public Function GetEnumerator() As IEnumerator _
  Implements IEnumerable.GetEnumerator
    Return New IntEnumerator()
  End Function
End Class

Class IntEnumerator
  Implements IEnumerator

  Private index As Integer = -1

  Public ReadOnly Property Current As Object _
  Implements IEnumerator.Current
    Get
      Select Case index
        Case 0 : Return "foo"
        Case 1 : Return "bar"
        Case 2 : Return "baz"
      End Select

      Throw New InvalidOperationException()
    End Get
  End Property

  Public Function MoveNext() As Boolean _
  Implements IEnumerator.MoveNext
    index += 1

    If index <= 2 Then
      Return True
    Else
      Return False
    End If
  End Function

  Public Sub Reset() Implements _
  IEnumerator.Reset
    Throw New NotSupportedException()
  End Sub
End Class

Class Sample
  Shared Sub Main()
    For Each e As String In New IntEnumerable()
      Console.WriteLine(e)
    Next
  End Sub
End Class
実行結果
foo
bar
baz

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

遅延実行

イテレータの動作は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);
    }
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  ' 0から4の値を生成するメソッド
  Shared Iterator Function GenerateNumbers() As IEnumerable(Of Integer)
    Yield 0
    Yield 1
    Yield 2
    Yield 3
    Yield 4
  End Function

  ' 列挙される値を倍にして返すメソッド
  Shared Iterator Function [Double](ByVal list As IEnumerable(Of Integer)) As IEnumerable(Of Integer)
    For Each e As Integer In list
      Yield e * 2
    Next
  End Function

  Shared Sub Main()
    For Each e As Integer In [Double](GenerateNumbers())
      Console.WriteLine(e)
    Next
  End Sub
End Class
実行結果
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);
    }
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  ' 0から4の値を生成するメソッド
  Shared Iterator Function GenerateNumbers() As IEnumerable(Of Integer)
    Console.WriteLine("yield 0") : Yield 0
    Console.WriteLine("yield 1") : Yield 1
    Console.WriteLine("yield 2") : Yield 2
    Console.WriteLine("yield 3") : Yield 3
    Console.WriteLine("yield 4") : Yield 4
  End Function

  ' 列挙される値を倍にして返すメソッド
  Shared Iterator Function [Double](ByVal list As IEnumerable(Of Integer)) As IEnumerable(Of Integer)
    For Each e As Integer In list
      Yield e * 2
    Next
  End Function

  Shared Sub Main()
    For Each e As Integer In [Double](GenerateNumbers())
      Console.WriteLine(e)
    Next
  End Sub
End Class
実行結果
yield 0
0
yield 1
2
yield 2
4
yield 3
6
yield 4
8

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

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

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

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