2010-10-12T04:20:42の更新内容

programming/netfx2/overview/enumeration/index.wiki.txt

current previous
1,1257 1,553
~
${smdncms:title,列挙操作と列挙子(IEnumerable, IEnumerator)}
${smdncms:title,コレクションの列挙(IEnumerable, IEnumerator)}
+
${smdncms:keywords,IEnumerable,IEnumerator,foreach,for,列挙子,反復子,イテレータ}
 
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
-
#googleadunit
-

          
-
*ForとFor Each
-
VB.NETではFor文には二種類あります。 それは通常の繰り返しのためのForステートメントと、要素の列挙ためのFor Eachステートメントです。 For EachステートメントはVBのころから導入されていましたが、VB.NETでも引き続き使用され、C#でもforeachとして導入されました。 では、そのFor Eachの動作から見てみたいと思います。 For EachステートメントではIn以降で指定されるコレクションや配列の中にある全ての要素の列挙を繰り返します。 Integer型の配列でForと For Eachを使った例を次に示します。
-

          
-
#code(vb){{
-
Imports System
-

          
-
' アプリケーションのエントリーポイントのためのクラス
-
Public NotInheritable Class Enumeration
-

          
-
    ' エントリーポイント
-
    Public Shared Function Main(ByVal args() As String) As Integer
-

          
-
        Dim arr(4) As Integer
-

          
-
        Dim i As Integer
-

          
-
        ' 配列に値を与える
-
        For i = 0 To arr.Length - 1
-

          
-
            arr(i) = i
-

          
-
        Next
-

          
-
        ' For ステートメントで配列の値を表示する
-
        Console.WriteLine("Enumerate by 'For' statement.")
-

          
-
        For i = 0 To arr.Length - 1
-

          
-
            Console.WriteLine(arr(i))
 

        

        
~
C#やVB.NETでは、コレクションの各要素に対する繰り返しを行う構文として、foreach文/For Eachステートメントが用意されています。 また、.NET FrameworkにはIEnumerableとIEnumeratorという列挙処理をサポートするためのインターフェイスが用意されています。 ここでは、列挙操作と列挙子のインターフェイスについて解説します。
        Next
 

        

        
~
-関連するページ
        ' For Each ステートメントで配列の値を表示する
~
--[[programming/netfx2/overview/collection]]
        Console.WriteLine("Enumerate by 'For Each'statement.")
+
--[[programming/netfx2/overview/clone_dispose]]
+
--[[programming/vb.net/basics/03_statement]]
+
--[[programming/tips/paralellforeach_with_queueuserworkitem]]
 

        

        
~
#googleadunit
        For Each i In arr
 

        

        
~
----
            Console.WriteLine(i)
 

        

        
~
*列挙操作と列挙子
        Next
+
C#やVB.NETでは、繰り返し構文としてよく似た二つの構文が用意されています。 それは単純な繰り返しのためのfor文/Forステートメントと、コレクションの列挙ためのforeach文/For Eachステートメントです。 For EachステートメントはVBのころから導入されていましたが、VB.NETでも引き続きサポートされC#でもforeach文として導入されています。
 

        

        
~
ここでは、foreach文による列挙操作と列挙子(IEnumerable, IEnumerator)の関係について見ていきます。
        Return 0
 

        

        
~
**for文とforeach文
    End Function
+
まずは、for文とforeach文で何が異なるのか、二つの繰り返し構文の違いを見ておきます。 for文では条件式が偽(false)になるまで繰り返し変数をインクリメント(またはデクリメント)し、foreach文ではin以降で指定されるコレクションや配列の中にあるすべての要素の列挙を行います。 以下の例では、int型の配列を用いてfor文とforeach文で列挙を行っています。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    int[] arr = new int[] {0, 1, 2, 3, 4};
+

          
+
    // for文で配列の要素を表示する
+
    Console.WriteLine("for");
+

          
+
    for (int i = 0; i < arr.Length; i++)
+
    {
+
      Console.WriteLine(arr[i]);
+
    }
+

          
+
    // foreach文で配列の要素を表示する
+
    Console.WriteLine("foreach");
+

          
+
    foreach (int e in arr)
+
    {
+
      Console.WriteLine(e);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
+
Class Sample
+
  Shared Sub Main()
+
    Dim arr() As Integer = New Integer() {0, 1, 2, 3, 4}
+

          
+
    ' Forステートメントで配列の要素を表示する
+
    Console.WriteLine("For")
+

          
+
    For i As Integer = 0 To arr.Length - 1
+
      Console.WriteLine(arr(i))
+
    Next
+

          
+
    ' For Eachステートメントで配列の要素を表示する
+
    Console.WriteLine("For Each")
+

          
+
    For Each e As Integer In arr
+
      Console.WriteLine(e)
+
    Next
+
  End Sub
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
for
Enumerate by 'For' statement.
 
0
0
 
1
1
 
2
2
 
3
3
 
4
4
~
foreach
Enumerate by 'For Each' statement.
 
0
0
 
1
1
 
2
2
 
3
3
 
4
4
-
Press any key to continue
 
}}
}}
 

        

        
~
この例において、for文とforeach文での違いは、for文では変数iを使って配列のインデックスを指定することで列挙しているのに対し、foreach文では指定した変数eに配列の各要素を格納させることで列挙しているという点です。
このように、For も For Eachも同じように列挙できます。 こうなるとFor と For Eachは何が違うのかよくわからなくなります。 そこで、配列の簡易ラッパークラスを使ってFor Eachが使えなくなる状況を作り出してみます。
 

        

        
+
もう少し違いを見るために、配列をQueueクラスに置き換えてみます。 Queueクラスはインデクサをサポートしていないので、for文を用いてインデックスを指定した列挙は出来ません。 そのため、以下のコードにおいて、foreach文による列挙の部分は問題なく動作しますが、for文による列挙の部分でコンパイルエラーとなります。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.Collections.Generic;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Queue<int> queue = new Queue<int>(new int[] {0, 1, 2, 3, 4});
+

          
+
    // for文でQueueに格納されている要素を表示する
+
    Console.WriteLine("for");
+

          
+
    for (int i = 0; i < queue.Count; i++)
+
    {
+
      // error CS0021: 角かっこ [] 付きインデックスを'System.Collections.Generic.Queue<int>'型の式に適用することはできません。
+
      Console.WriteLine(queue[i]);
+
    }
+

          
+
    // foreach文でQueueに格納されている要素を表示する
+
    Console.WriteLine("foreach");
+

          
+
    foreach (int e in queue)
+
    {
+
      Console.WriteLine(e);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
+
Imports System.Collections.Generic
 

        

        
~
Class Sample
Public Class IntegerArray
~
  Shared Sub Main()

          
~
    Dim queue As New Queue(Of Integer)(New Integer() {0, 1, 2, 3, 4})
    ' ラップするための内部配列
~

          
    Private m_Array() As Integer
~
    ' ForステートメントでQueueに格納されている要素を表示する

          
~
    Console.WriteLine("For")
    ' コンストラクタ
~

          
    Public Sub New()
~
    For i As Integer = 0 To queue.Count - 1

          
~
      ' error BC30367: クラス 'System.Collections.Generic.Queue(Of Integer)' には既定のプロパティがないため、インデックス処理を実行できません。
        ' 既定の配列サイズ
~
      Console.WriteLine(queue(i))
        Me.New(&H10)
~
    Next

          
~

          
    End Sub
~
    ' For EachステートメントでQueueに格納されている要素を表示する

          
~
    Console.WriteLine("For Each")
    Public Sub New(ByVal capacity As Integer)
~

          

          
~
    For Each e As Integer In queue
        ' capacityで指定されたサイズの配列を作成
~
      Console.WriteLine(e)
        ReDim m_Array(capacity - 1)
~
    Next

          
~
  End Sub
    End Sub
-

          
-

          
-
    ' 配列へのアクセサとしてのデフォルトプロパティ
-
    Default Public Property Item(ByVal index As Integer) As Integer
-

          
-
        Get
-

          
-
            If 0 <= index AndAlso index < m_Array.Length Then
-

          
-
                ' 配列のインデックスの範囲内であればそのインデックスの値を返す。
-
                Return m_Array(index)
-

          
-
            Else
-

          
-
                ' そうでなければ 0 を返すことにする。
-
                Return 0
-

          
-
            End If
-

          
-
        End Get
-

          
-
        Set(ByVal Value As Integer)
-

          
-
            If 0 <= index AndAlso index < m_Array.Length Then
-

          
-
                ' 配列のインデックスの範囲内であればそのインデックスに代入する
-
                m_Array(index) = Value
-

          
-
            End If
-

          
-
            ' そうでない場合は無視する
-

          
-
        End Set
-

          
-
    End Property
-

          
-
    ' 長さを返す
-
    Public ReadOnly Property Length() As Integer
-

          
-
        Get
-

          
-
            Return m_Array.Length
-

          
-
        End Get
-

          
-
    End Property
-

          
 
End Class
End Class
+
}}
+
#tabpage-end
 

        

        
~
このように、for文ではインデックスが指定できないと列挙出来ませんが、foreach文ではインデックスが指定出来なくても列挙が出来ます。 ではforeach文で列挙できない場合とはどのような場合なのか、その違いを明らかにするために、別の例を挙げてみます。
' アプリケーションのエントリーポイントのためのクラス
-
Public NotInheritable Class Enumeration
-

          
-
    ' エントリーポイント
-
    Public Shared Function Main(ByVal args() As String) As Integer
-

          
-
        Dim arr As New IntegerArray(5)
-

          
-
        Dim i As Integer
-

          
-
        ' 配列に値を与える
-
        For i = 0 To arr.Length - 1
-

          
-
            arr(i) = i
-

          
-
        Next
-

          
-
        ' For ステートメントで配列の値を表示する
-
        Console.WriteLine("Enumerate by 'For' statement.")
 

        

        
~
次のコードでは、インデクサをサポートする配列の簡易ラッパークラスを作り、for文とforeach文を用いた列挙操作を記述しています。
        For i = 0 To arr.Length - 1
 

        

        
~
#tabpage(C#)
            Console.WriteLine(arr(i))
+
#code(cs){{
+
using System;
+

          
+
// インデクサをサポートする配列のラッパークラス
+
class IntArray
+
{
+
  private int[] arr;
+

          
+
  public IntArray(int[] arr)
+
  {
+
    this.arr = arr;
+
  }
+

          
+
  // インデクサ
+
  public int this[int index]
+
  {
+
    get { return arr[index]; }
+
    set { arr[index] = value; }
+
  }
+

          
+
  // 長さ
+
  public int Length
+
  {
+
    get { return arr.Length; }
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    IntArray arr = new IntArray(new int[] {0, 1, 2, 3, 4});
+

          
+
    // for文で格納されている要素を表示する
+
    Console.WriteLine("for");
+

          
+
    for (int i = 0; i < arr.Length; i++)
+
    {
+
      Console.WriteLine(arr[i]);
+
    }
+

          
+
    // foreach文で格納されている要素を表示する
+
    Console.WriteLine("foreach");
+

          
+
    // error CS1579: foreach ステートメントは、'IntArray' が 'GetEnumerator' のパブリック定義を含んでいないため、型 'IntArray' の変数に対して使用できません。
+
    foreach (int e in arr)
+
    {
+
      Console.WriteLine(e);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
' インデクサをサポートする配列のラッパークラス
        Next
+
Class IntArray
+
  Private arr() As Integer
+

          
+
  Public Sub New(ByVal arr() As Integer)
+
    MyClass.arr = arr
+
  End Sub
+

          
+
  ' インデクサとなるデフォルトプロパティ
+
  Public Default Property Item(ByVal index As Integer) As Integer
+
    Get
+
      Return arr(index)
+
    End Get
+
    Set(ByVal value As Integer)
+
      arr(index) = Value
+
    End Set
+
  End Property
+

          
+
  ' 長さ
+
  Public ReadOnly Property Length() As Integer
+
    Get
+
      Return arr.Length
+
    End Get
+
  End Property
+
End Class
 

        

        
~
Class Sample
        ' For Each ステートメントで配列の値を表示する
~
  Shared Sub Main()
        Console.WriteLine("Can not enumerate by 'For Each' statement.")
+
    Dim arr As New IntArray(New Integer() {0, 1, 2, 3, 4})
+

          
+
    ' Forステートメントで格納されている要素を表示する
+
    Console.WriteLine("For")
+

          
+
    For i As Integer = 0 To arr.Length - 1
+
      Console.WriteLine(arr(i))
+
    Next
+

          
+
    ' For Eachステートメントで格納されている要素を表示する
+
    Console.WriteLine("For Each")
+

          
+
    ' error BC32023: 式の型は 'IntArray' で、コレクション型ではありません。
+
    For Each e As Integer In arr
+
      Console.WriteLine(e)
+
    Next
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
コメントでも書いている通り、このコードはfor文での列挙は問題なく出来ますが、foreach文での列挙はコンパイルエラーとなります。
        'For Each i In arr
 

        

        
~
ここまでの例を見て分かる通り、foreach文で列挙できるかどうかは、配列やコレクションのようにインデクサをサポートしているかどうかとは別の理由によるものであるということが分かると思います。
        '    Console.WriteLine(i)
 

        

        
~
**foreach文とIEnumerable, IEnumeratorの関係
        'Next
+
foreach文/For Eachステートメントでの列挙操作を出来るようにするためには、列挙される型が&msdn(netfx,type,System.Collections.IEnumerable){IEnumerableインターフェイス};を実装していなければなりません。 IEnumerableインターフェイスは、型が''列挙可能''(enumerable)であることを表すためのインターフェイスです。 型がこのインターフェイスを実装している場合、foreach文での列挙が出来るようになります。 配列やコレクションクラスがforeach文で列挙できるのは、配列の実体であるArrayクラスや個々のコレクションクラスがIEnumerableインターフェイスを実装しているためです。
 

        

        
~
IEnumerableインターフェイスには唯一のメソッド&msdn(netfx,method,System.Collections.IEnumerable.GetEnumerator){GetEnumerator};が存在します。 このメソッドは、列挙操作を行うための''列挙子''(enumerator)である&msdn(netfx,type,System.Collections.IEnumerator){IEnumeratorインターフェイス};を取得するためのメソッドです。
        Return 0
+

          
+
この二つのインターフェイスとforeach文の関係と動作を整理すると、次のようになります。
+
+foreach文での列挙を行う際に、型が''列挙可能''(enumerable)かどうか調べる (IEnumerableインターフェイスを実装しているか調べる)
+
+列挙可能なら、''列挙子''(enumerator)を取得する (IEnumerable.GetEnumeratorメソッドでIEnumeratorを取得する)
+
+取得した列挙子を使って列挙操作を行う
+

          
+
個々の詳細は後述するとして、先の例をIEnumerableインターフェイスを実装したものに書き換えてみます。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.Collections;
+

          
+
// インデクサをサポートする配列のラッパークラス
+
class IntArray : IEnumerable
+
{
+
  private int[] arr;
+

          
+
  public IntArray(int[] arr)
+
  {
+
    this.arr = arr;
+
  }
+

          
+
  // インデクサ
+
  public int this[int index]
+
  {
+
    get { return arr[index]; }
+
    set { arr[index] = value; }
+
  }
+

          
+
  // 長さ
+
  public int Length
+
  {
+
    get { return arr.Length; }
+
  }
+

          
+
  // IEnumerable.GetEnumeratorの実装
+
  public IEnumerator GetEnumerator()
+
  {
+
    // Array.GetEnumerator()の戻り値を流用する
+
    return arr.GetEnumerator();
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    IntArray arr = new IntArray(new int[] {0, 1, 2, 3, 4});
+

          
+
    // for文で格納されている要素を表示する
+
    Console.WriteLine("for");
+

          
+
    for (int i = 0; i < arr.Length; i++)
+
    {
+
      Console.WriteLine(arr[i]);
+
    }
+

          
+
    // foreach文で格納されている要素を表示する
+
    Console.WriteLine("foreach");
+

          
+
    foreach (int e in arr)
+
    {
+
      Console.WriteLine(e);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections
 

        

        
~
' インデクサをサポートする配列のラッパークラス
    End Function
+
Class IntArray
+
  Implements IEnumerable
+

          
+
  Private arr() As Integer
+

          
+
  Public Sub New(ByVal arr() As Integer)
+
    MyClass.arr = arr
+
  End Sub
+

          
+
  ' インデクサとなるデフォルトプロパティ
+
  Public Default Property Item(ByVal index As Integer) As Integer
+
    Get
+
      Return arr(index)
+
    End Get
+
    Set(ByVal value As Integer)
+
      arr(index) = Value
+
    End Set
+
  End Property
+

          
+
  ' 長さ
+
  Public ReadOnly Property Length() As Integer
+
    Get
+
      Return arr.Length
+
    End Get
+
  End Property
+

          
+
  ' IEnumerable.GetEnumeratorの実装
+
  Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
+
    ' Array.GetEnumerator()の戻り値を流用する
+
    Return arr.GetEnumerator()
+
  End Function
+
End Class
 

        

        
+
Class Sample
+
  Shared Sub Main()
+
    Dim arr As New IntArray(New Integer() {0, 1, 2, 3, 4})
+

          
+
    ' Forステートメントで格納されている要素を表示する
+
    Console.WriteLine("For")
+

          
+
    For i As Integer = 0 To arr.Length - 1
+
      Console.WriteLine(arr(i))
+
    Next
+

          
+
    ' For Eachステートメントで格納されている要素を表示する
+
    Console.WriteLine("For Each")
+

          
+
    For Each e As Integer In arr
+
      Console.WriteLine(e)
+
    Next
+
  End Sub
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
for
Enumerate by 'For' statement.
+
0
+
1
+
2
+
3
+
4
+
foreach
 
0
0
 
1
1
 
2
2
 
3
3
 
4
4
-
Can not enumerate by 'For Each' statement.
-
Press any key to continue
 
}}
}}
 

        

        
~
このように、IEnumerableインターフェイスを実装することで、foreach文による列挙が行えるようになります。
IntegerArrayクラスはふつうのInteger型の配列と同様の振る舞いをします。 しかし、エラーによりFor Eachはできません。 そのエラーを見ると次のようになっています。
-

          
-
#ref(0.png,エラーメッセージ)
-

          
-
つまり、In 以降で指定すべきオブジェクトはコレクション型でなければならないが、コンパイラはarrがコレクション型ではないと言っていることになります。
 

        

        
~
*IEnumerableとIEnumerator
*IEnumerableの実装
~
型が列挙可能となるためには、IEnumerableインターフェイスを実装し、IEnumeratorインターフェイスを返す必要があることが分かりました。 ここでは、IEnumerableとIEnumeratorを実装する方法と、これらのインターフェイスの動作について見ていきます。 また、これらのインターフェイスのジェネリック版であるIEnumerable<T>インターフェイスとIEnumerator<T>インターフェイスについても見ていきます。
ヘルプでFor Eachステートメントについて調べると次のようなことが書いてありました。
 

        

        
~
**IEnumerableとIEnumerator
-For Each...Next ステートメントは、式の要素に基づいてループを実行します。For Each ステートメントの式によって返される値の型は、下で定義する "コレクション型" である必要があります。また、コレクションの要素型から繰り返し変数の型への、暗黙の型変換が必要です。
~
IEnumerableとIEnumeratorを実装するために、それぞれのインターフェイスの仕様について見ていきます。 まず、型を列挙可能にするためのインターフェイスIEnumerableは、次の仕様を満たすように実装する必要があります。
-System.IEnumerable を実装するか、または次の条件をすべて満たしている場合、型 C はコレクション型になります。
-
--C に、シグネチャが GetEnumerator() で、型 E. を返す Public インスタンス メソッドが含まれる。
-
--E に、シグネチャが MoveNext() で、戻り値の型が Boolean である Public インスタンス メソッドが含まれる。
-
--E に、取得関数を持つ Current という名前の Public インスタンス プロパティが含まれる。このプロパティの型は、コレクション型の要素型と呼ばれます。
-

          
-
つまり簡単にまとめてしまうと、For Eachを使えるようにするにはその型がコレクション型である必要があり、コレクション型にするためにはIEnumerableインターフェイスを実装していればよい、といっていることになります。
-

          
-
それでは早速、先ほどのIntegerArrayクラスにIEnumerableインターフェイスを実装してみます。
 

        

        
+
:&msdn(netfx,type,System.Collections.IEnumerable){IEnumerableインターフェイス};|以下の1つのメンバを実装する必要がある。
+
::&msdn(netfx,method,System.Collections.IEnumerable.GetEnumerator){GetEnumeratorメソッド};|インターフェイスを実装する型の要素を列挙するための列挙子IEnumeratorを返す。
+

          
+
続いて要素を列挙するためのインターフェイスIEnumeratorは、次の仕様を満たすように実装する必要があります。
+

          
+
:&msdn(netfx,type,System.Collections.IEnumerator){IEnumeratorインターフェイス};|以下の3つのメンバを実装する必要がある。
+
::&msdn(netfx,member,System.Collections.IEnumerator.Current){Currentプロパティ};|列挙子が指し示すコレクション内の要素をObject型で返す。 読み取り専用プロパティ。
+
::&msdn(netfx,method,System.Collections.IEnumerator.MoveNext){MoveNextメソッド};|列挙子が指し示す要素の位置を次に進める。 位置がコレクションの末尾を超えた場合(=これ以上列挙できる要素が無い場合)はfalse、それ以外の場合はtrueを返す。
+
::&msdn(netfx,method,System.Collections.IEnumerator.Reset){Resetメソッド};|列挙子が指し示す要素の位置を最初の位置に戻し、列挙子の状態を初期状態に戻す。 (必ずしも実装する必要はなく、NotSupportedExceptionをスローしてもよい)
+

          
+
IEnumerator.ResetメソッドはCOM相互運用で使用されるメソッドで、foreach文などによる列挙操作では呼び出されません。 そのため、NotSupportedExceptionをスローするように実装してもよいとされています。 また、列挙操作中にコレクションが変更(追加・削除・挿入など)され、列挙操作を継続できない場合は、InvalidOperationExceptionをスローするようにします。
+

          
+
早速、上記の仕様を満たすクラスを用意して、列挙操作を行ってみます。 以下の例は、列挙可能なコレクションに見立てたクラスEnumerableと、コレクションの列挙子に見立てたクラスEnumeratorを作成したものです。 この例では、Enumerableクラスはコレクションとしての実体は持っていませんが、Enumerableクラスで作成されるEnumeratorクラスは{0, 1, 2}の3つの要素を列挙します。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.Collections;
+

          
+
class Enumerable : IEnumerable
+
{
+
  // IEnumerable.GetEnumeratorの実装
+
  public IEnumerator GetEnumerator()
+
  {
+
    // IEnumerableを実装するクラスEnumeratorのインスタンスを返す
+
    return new Enumerator();
+
  }
+
}
+

          
+
class Enumerator : IEnumerator
+
{
+
  // 現在の位置を表す変数
+
  int pos = -1;
+

          
+
  // IEnumerator.MoveNextの実装
+
  public bool MoveNext()
+
  {
+
    // 現在の位置を1増やす
+
    pos++;
+

          
+
    if (pos == 3)
+
    {
+
      // 位置が3になったら、それ以上列挙できる要素は無いものとしてfalseを返す
+
      return false;
+
    }
+
    else
+
    {
+
      // それ以外の場合は、まだ列挙できる要素があるものとしてtrueを返す
+
      return true;
+
    }
+
  }
+

          
+
  // IEnumerator.Currentの実装
+
  public object Current
+
  {
+
    // 現在の位置自体を要素の値として返す
+
    get { return pos; }
+
  }
+

          
+
  // IEnumerator.Resetの実装
+
  public void Reset()
+
  {
+
    // 実装は省略、NotSupportedExceptionをスローするようにする
+
    throw new NotSupportedException();
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Enumerable enumerable = new Enumerable();
+

          
+
    foreach (int e in enumerable)
+
    {
+
      Console.WriteLine(e);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
+
Imports System.Collections
 

        

        
~
Class Enumerable
Public Class IntegerArray
~
  Implements IEnumerable

          
-
    ' IEnumerableを実装
-
    Implements IEnumerable
-

          
-
    ' ラップするための内部配列
-
    Private m_Array() As Integer
-

          
-
    ' コンストラクタ
-
    Public Sub New()
-

          
-
        ' 既定の配列サイズ
-
        Me.New(&H10)
 

        

        
~
  ' IEnumerable.GetEnumeratorの実装
    End Sub
~
  Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator

          
~
    ' IEnumerableを実装するクラスEnumeratorのインスタンスを返す
    Public Sub New(ByVal capacity As Integer)
~
    Return New Enumerator()

          
~
  End Function
        ' capacityで指定されたサイズの配列を作成
~
End Class
        ReDim m_Array(capacity - 1)
-

          
-
    End Sub
-

          
-

          
-
    ' 配列へのアクセサとしてのデフォルトプロパティ
-
    Default Public Property Item(ByVal index As Integer) As Integer
-

          
-
        Get
 

        

        
~
Class Enumerator
            If 0 <= index AndAlso index < m_Array.Length Then
+
  Implements IEnumerator
 

        

        
~
  ' 現在の位置を表す変数
                ' 配列のインデックスの範囲内であればそのインデックスの値を返す。
~
  Dim pos As Integer = -1
                Return m_Array(index)
 

        

        
~
  ' IEnumerator.MoveNextの実装
            Else
+
  Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
+
    ' 現在の位置を1増やす
+
    pos += 1
+

          
+
    If pos = 3 Then
+
      ' 位置が3になったら、それ以上列挙できる要素は無いものとしてFalseを返す
+
      Return False
+
    Else
+
      ' それ以外の場合は、まだ列挙できる要素があるものとしてTrueを返す
+
      Return True
+
    End If
+
  End Function
+

          
+
  ' IEnumerator.Currentの実装
+
  Public ReadOnly Property Current() As Object Implements IEnumerator.Current
+
    Get
+
      ' 現在の位置自体を要素の値として返す
+
      Return pos
+
    End Get
+
  End Property
+

          
+
  ' IEnumerator.Resetの実装
+
  Public Sub Reset() Implements IEnumerator.Reset
+
    ' 実装は省略、NotSupportedExceptionをスローするようにする
+
    Throw New NotSupportedException()
+
  End Sub
+
End Class
 

        

        
~
Class Sample
                ' そうでなければ 0 を返すことにする。
~
  Shared Sub Main()
                Return 0
+
    Dim enumerable As New Enumerable()
+

          
+
    For Each e As Integer In enumerable
+
      Console.WriteLine(e)
+
    Next
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt(実行結果){{
            End If
+
0
+
1
+
2
+
}}
 

        

        
~
IEnumerableとIEnumeratorの仕様と照らし合わせながら上記の実装と実行結果を見ていけば、各インターフェイスの役割と動作について理解できると思います。 列挙時の動作について整理すると、次のようになります。
        End Get
+
+(foreach文/For Eachステートメントなどで)IEnumerable.GetEnumeratorメソッドが呼ばれ、IEnumeratorが取得される。
+
+取得されたIEnumeratorのMoveNextメソッドが呼ばれ、要素の位置が次の位置に進められる。 このときの戻り値が、
+
++falseだった場合はそれ以上列挙できる要素がないため、列挙が終了する。
+
++trueだった場合はまだ列挙できる要素があるため、列挙が継続する。
+
+IEnumerator.Currentプロパティが参照され、現在の位置にある要素が取得される。
+
+2に戻り、列挙を繰り返す。
+

          
+
IEnumerableとIEnumeratorの動作と列挙操作について理解を深めるために、違った例を挙げてみます。 次の例は、for文/foreach文を使わずに配列を列挙する例です。 配列を一旦IEnumerableにキャストし、GetEnumeratorでIEnumeratorを取得することで配列内の要素を列挙しています。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.Collections;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    int[] arr = new int[] {0, 1, 2};
+

          
+
    // IEnumerableにキャスト
+
    IEnumerable enumerable = arr;
+

          
+
    // IEnumeratorを取得
+
    IEnumerator enumerator = enumerable.GetEnumerator();
+
    int e;
+

          
+
    // MoveNextがfalseを返すまで繰り返す
+
    while (enumerator.MoveNext())
+
    {
+
      // 現在の値を取得
+
      e = (int)enumerator.Current;
+

          
+
      Console.WriteLine(e);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections
 

        

        
~
Class Sample
        Set(ByVal Value As Integer)
+
  Shared Sub Main()
+
    Dim arr() As Integer = New Integer() {0, 1, 2}
+

          
+
    ' IEnumerableにキャスト
+
    Dim enumerable As IEnumerable = arr
+

          
+
    ' IEnumeratorを取得
+
    Dim enumerator As IEnumerator = enumerable.GetEnumerator()
+
    Dim e As Integer
+

          
+
    ' MoveNextがFalseを返すまで繰り返す
+
    While enumerator.MoveNext()
+
      ' 現在の値を取得
+
      e = CInt(enumerator.Current)
+

          
+
      Console.WriteLine(e)
+
    End While
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt(実行結果){{
            If 0 <= index AndAlso index < m_Array.Length Then
+
0
+
1
+
2
+
}}
 

        

        
~
最後に、もう少し具体的な実装の例を挙げます。 以下の例では、指定された長さの配列をラップするクラスIntArrayと、その列挙子IntArrayEnumeratorを作成しています。 IntArrayEnumeratorはIntArray以外のクラスから使われることは無いので、IntArrayの入れ子クラスとして実装しています。
                ' 配列のインデックスの範囲内であればそのインデックスに代入する
-
                m_Array(index) = Value
 

        

        
~
#tabpage(C#)
            End If
+
#code(cs){{
+
using System;
+
using System.Collections;
+

          
+
class IntArray : IEnumerable
+
{
+
  private int[] arr;
+

          
+
  public IntArray(int length)
+
  {
+
    arr = new int[length];
+
  }
+

          
+
  public int this[int index]
+
  {
+
    get { return arr[index]; }
+
    set { arr[index] = value; }
+
  }
+

          
+
  public int Length
+
  {
+
    get { return arr.Length; }
+
  }
+

          
+
  public IEnumerator GetEnumerator()
+
  {
+
    // 現在のインスタンスを渡して列挙子を作成
+
    return new IntArrayEnumerator(this);
+
  }
+

          
+
  // IntArray用の列挙子
+
  private class IntArrayEnumerator : IEnumerator
+
  {
+
    private IntArray arr; // この列挙子が列挙するIntArrayのインスタンス
+
    private int index = -1; // 列挙子の現在の位置
+

          
+
    public IntArrayEnumerator(IntArray arr)
+
    {
+
      this.arr = arr;
+
    }
+

          
+
    public bool MoveNext()
+
    {
+
      index++;
+

          
+
      if (index == arr.Length)
+
      {
+
        return false;
+
      }
+
      else
+
      {
+
        return true;
+
      }
+
    }
+

          
+
    public object Current
+
    {
+
      get { return arr[index]; }
+
    }
+

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

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    IntArray arr = new IntArray(10); // 要素数10で初期化
+

          
+
    // 値を設定
+
    for (int i = 0; i < arr.Length; i++)
+
    {
+
      arr[i] = i;
+
    }
+

          
+
    // 要素を列挙
+
    foreach (int e in arr)
+
    {
+
      Console.WriteLine(e);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections
 

        

        
~
Class IntArray
            ' そうでない場合は無視する
+
  Implements IEnumerable
 

        

        
~
  Private arr() As Integer
        End Set
 

        

        
~
  Public Sub New(ByVal length As Integer)
    End Property
+
    arr = New Integer(length - 1) {}
+
  End Sub
+

          
+
  Public Default Property Item(ByVal index As Integer) As Integer
+
    Get
+
      Return arr(index)
+
    End Get
+
    Set(ByVal value As Integer)
+
      arr(index) = value
+
    End Set
+
  End Property
+

          
+
  Public ReadOnly Property Length As Integer
+
    Get
+
      Return arr.Length
+
    End Get
+
  End Property
+

          
+
  Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
+
    ' 現在のインスタンスを渡して列挙子を作成
+
    Return New IntArrayEnumerator(Me)
+
  End Function
+

          
+
  ' IntArray用の列挙子
+
  Private Class IntArrayEnumerator
+
    Implements IEnumerator
 

        

        
~
    Private arr As IntArray ' この列挙子が列挙するIntArrayのインスタンス
    ' 長さを返す
~
    Private index As Integer = -1 ' 列挙子の現在の位置
    Public ReadOnly Property Length() As Integer
 

        

        
~
    Public Sub New(ByVal arr As IntArray)
        Get
+
      MyClass.arr = arr
+
    End Sub
 

        

        
~
    Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
            Return m_Array.Length
+
      index += 1
 

        

        
~
      If index = arr.Length
        End Get
+
        Return False
+
      Else
+
        Return True
+
      End If
+
    End Function
 

        

        
+
    Public ReadOnly Property Current As Object Implements IEnumerator.Current
+
      Get
+
        Return arr(index)
+
      End Get
 
    End Property
    End Property
 

        

        
~
    Public Sub Reset() Implements IEnumerator.Reset
    ' IEnumerable.GetEnumerator()の実装
~
      Throw New NotSupportedException()
    Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
~
    End Sub

          
~
  End Class
        ' 配列型のGetEnumerator()を流用する
~
End Class
        Return m_Array.GetEnumerator()
-

          
-
    End Function
 

        

        
+
Class Sample
+
  Shared Sub Main()
+
    Dim arr As New IntArray(10) ' 要素数10で初期化
+

          
+
    ' 値を設定
+
    For i As Integer = 0 To arr.Length - 1
+
      arr(i) = i
+
    Next
+

          
+
    ' 要素を列挙
+
    For Each e As Integer In arr
+
      Console.WriteLine(e)
+
    Next
+
  End Sub
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
-
Enumerate by 'For' statement.
-
0
-
1
-
2
-
3
-
4
-
Enumerate by 'For Each' statement.
 
0
0
 
1
1
 
2
2
 
3
3
 
4
4
~
5
Press any key to continue
+
6
+
7
+
8
+
9
 
}}
}}
 

        

        
~
**IEnumerable<T>とIEnumerator<T>
このサンプルを実行する前に、Main()メソッドでは先ほどまで使えなかったFor Eachを再び使えるようにコメント化を解除しています。 IEnumarableインターフェイスを実装する場合は一つのメソッド GetEnumerator()を実装しなければなりません。 このメソッドは、簡単に言ってしまうとFor Eachをするために必要な処理を行ってくれる「列挙子」というものを取得するためのものです。 この例では配列型のm_Arrayから列挙子を取得していますが、実は配列型がFor Eachする事ができるのは、この列挙子を取得するためのメソッドを持っているためなのです。
~
IEnumerableとIEnumeratorにはそのジェネリック版となるインターフェイス&msdn(netfx,id,9eekhta0){IEnumerable<T>};と&msdn(netfx,id,78dfe2yb){IEnumerable<T>};が存在します。 先に実装例を見ることで、相違点を見ていきます。 以下の例は、先に挙げたIEnumerable・IEnumeratorでの実装例をIEnumerable<T>・IEnumerator<T>を使って書き換えたものです。

          
-
*IEnumeratorの正体
-
IEnumerable.GetEnumerator()メソッドは列挙子を取得するためのものと説明しましたが、その戻り値である列挙子は IEnumerator型です。 IEnumerableと名前が似ているので注意して下さい。 IEnumeratorはその名のとおり列挙を行うためのもので、これを取得することができればFor Eachする事ができるとしましたが、IEnumeratorの正体が分からないまま使っていても気分が悪いので、その実体を調べてみたいと思います。
-

          
-
IEnumeratorを実装するに当たり、実装するクラスは次の三つのメンバを実装しなければなりません。
-

          
-
:列挙子を初期状態に設定するメソッド: Public Sub Reset()|列挙子をコレクションの最初の要素の前の位置に設定する。
-
:列挙子を次の要素に移動するメソッド: Public Function MoveNext() As Boolean|列挙子をコレクションの次の要素に進め、コレクションの範囲を超えた場合はFalseを返す。
-
:現在列挙子が指し示すコレクションの要素を取得する読み取り専用プロパティ: Public ReadOnly Property Current() As Object|現在列挙子が指し示すコレクションの要素を返す。
-

          
-
実際のFor Eachではこれら三つのメンバが使用されます。 まず、For Eachが開始される時点でReset()メソッドが呼び出されます。 続いて、最初の要素が取り出される前にMoveNext()メソッドを呼び出し、列挙子に次の要素、つまり最初の要素を指し示させます。 この時点でCurrentプロパティからコレクションの値が読みとられます。 これ以降はFor EachのNextに達する度にMoveNext()、Currentが呼び出されます。 ここで、MoveNext()がFalseを返したときはコレクションがそれ以上要素を持っていないということなので、この時点で列挙が終了します。
-

          
-
それでは、IEnumeratorの詳細がわかったところで実装してみます。 ここではIEnumeratorを実装するクラスとして IntegerArrayEnumeratorクラスを作成しましたが、IntegerArrayクラスの列挙以外には使われる必要がないのでPrivateなクラスとしてIntegerArrayクラスの中で宣言しています。
 

        

        
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.Collections;
+
using System.Collections.Generic;
+

          
+
class Enumerable : IEnumerable<int>
+
{
+
  // IEnumerable<int>.GetEnumeratorの実装
+
  public IEnumerator<int> GetEnumerator()
+
  {
+
    return new Enumerator();
+
  }
+

          
+
  // IEnumerable.GetEnumeratorの実装
+
  IEnumerator IEnumerable.GetEnumerator()
+
  {
+
    // IEnumerable<int>.GetEnumeratorの実装を使う
+
    return GetEnumerator();
+
  }
+
}
+

          
+
class Enumerator : IEnumerator<int>
+
{
+
  int pos = -1;
+

          
+
  // IEnumerator<int>.MoveNextの実装
+
  public bool MoveNext()
+
  {
+
    pos++;
+

          
+
    if (pos == 3)
+
    {
+
      return false;
+
    }
+
    else
+
    {
+
      return true;
+
    }
+
  }
+

          
+
  // IEnumerator<int>.Currentの実装
+
  public int Current
+
  {
+
    get { return pos; }
+
  }
+

          
+
  // IEnumerator.Currentの実装
+
  object IEnumerator.Current
+
  {
+
    // IEnumerator<int>.Currentの値を返す
+
    get { return Current; }
+
  }
+

          
+
  // IEnumerator<int>.Resetの実装
+
  public void Reset()
+
  {
+
    throw new NotSupportedException();
+
  }
+

          
+
  // IEnumerator<int>.Disposeの実装
+
  public void Dispose()
+
  {
+
    Console.WriteLine("disposed");
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Enumerable enumerable = new Enumerable();
+

          
+
    foreach (int e in enumerable)
+
    {
+
      Console.WriteLine(e);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
+
Imports System.Collections
+
Imports System.Collections.Generic
 

        

        
~
Class Enumerable
Public Class IntegerArray
~
  Implements IEnumerable(Of Integer)

          
-
    ' IEnumerableを実装
-
    Implements IEnumerable
-

          
-

          
-

          
-

          
-
    ' IntegerArray用の列挙子 ( 外部に公開する必要はない )
-
    Private Class IntegerArrayEnumerator
-

          
-
        ' IEnumeratorを実装
-
        Implements IEnumerator
-

          
-
        ' 列挙子の指す要素の位置
-
        Private m_Pointer As Integer
-

          
-
        ' 列挙すべき配列
-
        Private m_Array As Integer()
-

          
-
        ' コンストラクタ
-
        Public Sub New(ByVal array As Integer())
-

          
-
            m_Array = array
-

          
-
            ' 念のため列挙子を初期化
-
            MyClass.Reset()
-

          
-
        End Sub
-

          
-
        ' 現在列挙子の指す要素を取得する
-
        Public ReadOnly Property Current() As Object Implements IEnumerator.Current
-

          
-
            Get
-

          
-
                Return m_Array(m_Pointer)
-

          
-
            End Get
-

          
-
        End Property
-

          
-
        ' 列挙子をリセットする
-
        Public Sub Reset() Implements IEnumerator.Reset
-

          
-
            m_Pointer = m_Array.GetLowerBound(0) - 1
-

          
-
        End Sub
-

          
-
        ' 列挙子を次の要素に移動する
-
        Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
-

          
-
            m_Pointer += 1
-

          
-
            ' 列挙子の位置が配列の範囲を超えていたらFalseを返し、列挙を終了させる
-
            If m_Pointer <= m_Array.GetUpperBound(0) Then
-

          
-
                Return True
-

          
-
            Else
-

          
-
                Return False
-

          
-
            End If
-

          
-
        End Function
-

          
-
    End Class
-

          
-

          
-

          
-

          
-
    ' ラップするための内部配列
-
    Private m_Array() As Integer
-

          
-
    ' コンストラクタ
-
    Public Sub New()
-

          
-
        ' 既定の配列サイズ
-
        Me.New(&H10)
-

          
-
    End Sub
-

          
-
    Public Sub New(ByVal capacity As Integer)
-

          
-
        ' capacityで指定されたサイズの配列を作成
-
        ReDim m_Array(capacity - 1)
 

        

        
~
  ' IEnumerable(Of Integer).GetEnumeratorの実装
    End Sub
~
  Public Function GetEnumerator() As IEnumerator(Of Integer) Implements IEnumerable(Of Integer).GetEnumerator

          
~
    Return New Enumerator()

          
~
  End Function
    ' 配列へのアクセサとしてのデフォルトプロパティ
~

          
    Default Public Property Item(ByVal index As Integer) As Integer
~
  ' IEnumerable.GetEnumeratorの実装

          
~
  Private Function GetEnumeratorNonGeneric() As IEnumerator Implements IEnumerable.GetEnumerator
        Get
~
    ' IEnumerable(Of Integer).GetEnumeratorの実装を使う

          
~
    Return GetEnumerator()
            If 0 <= index AndAlso index < m_Array.Length Then
~
  End Function

          
~
End Class
                ' 配列のインデックスの範囲内であればそのインデックスの値を返す。
-
                Return m_Array(index)
-

          
-
            Else
-

          
-
                ' そうでなければ 0 を返すことにする。
-
                Return 0
-

          
-
            End If
-

          
-
        End Get
-

          
-
        Set(ByVal Value As Integer)
-

          
-
            If 0 <= index AndAlso index < m_Array.Length Then
-

          
-
                ' 配列のインデックスの範囲内であればそのインデックスに代入する
-
                m_Array(index) = Value
-

          
-
            End If
-

          
-
            ' そうでない場合は無視する
-

          
-
        End Set
-

          
-
    End Property
-

          
-
    ' 長さを返す
-
    Public ReadOnly Property Length() As Integer
-

          
-
        Get
-

          
-
            Return m_Array.Length
-

          
-
        End Get
-

          
-
    End Property
 

        

        
~
Class Enumerator
    ' IEnumerable.GetEnumerator()の実装
~
  Implements IEnumerator(Of Integer)
    Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
 

        

        
~
  Dim pos As Integer = -1
        ' 独自に作成した IntegerArrayEnumerator を使用する
-
        Return New IntegerArrayEnumerator(m_Array)
 

        

        
~
  ' IEnumerator(Of Integer).MoveNextの実装
    End Function
+
  Public Function MoveNext() As Boolean Implements IEnumerator(Of Integer).MoveNext
+
    pos += 1
+

          
+
    If pos = 3 Then
+
      Return False
+
    Else
+
      Return True
+
    End If
+
  End Function
+

          
+
  ' IEnumerator(Of Integer).Currentの実装
+
  Public ReadOnly Property Current() As Integer Implements IEnumerator(Of Integer).Current
+
    Get
+
      Return pos
+
    End Get
+
  End Property
+

          
+
  ' IEnumerator.Currentの実装
+
  Private ReadOnly Property CurrentNonGeneric() As Object Implements IEnumerator.Current
+
    Get
+
      ' IEnumerator(Of Integer).Currentの値を返す
+
      Return Current
+
    End Get
+
  End Property
+

          
+
  ' IEnumerator(Of Integer).Resetの実装
+
  Public Sub Reset() Implements IEnumerator(Of Integer).Reset
+
    Throw New NotSupportedException()
+
  End Sub
+

          
+
  ' IEnumerator(Of Integer).Disposeの実装
+
  Public Sub Dispose() Implements IEnumerator(Of Integer).Dispose
+
    Console.WriteLine("disposed")
+
  End Sub
+
End Class
 

        

        
+
Class Sample
+
  Shared Sub Main()
+
    Dim enumerable As New Enumerable()
+

          
+
    For Each e As Integer In enumerable
+
      Console.WriteLine(e)
+
    Next
+
  End Sub
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
-
Enumerate by 'For' statement.
 
0
0
 
1
1
 
2
2
~
disposed
3
-
4
-
Enumerate by 'For Each' statement.
-
0
-
1
-
2
-
3
-
4
-
Press any key to continue
 
}}
}}
 

        

        
~
IEnumerable<T>とIEnumerator<T>について、それぞれ特筆すべき点をまとめます。
実行に際してMain()メソッドには変更を加えていません。 出力結果を見てわかるとおり、配列からIEnumeratorを取得した場合と全く変わっていませんが、確実にFor Eachにより列挙が成されています。 今回作成したIntegerArrayEnumeratorの動作は恐らくほとんど配列のIEnumerator の実装と変わらないと思います。 しかし、IEnumerator実装をカスタマイズすることで、様々なクラスでFor Eachが使えるようになることは非常に有益なことではないでしょうか。 また、今回は全てInteger型の配列を扱ってきましたが、 IEnumerator及びIEnumerableはObject型で扱うのでどのような型でもコレクションにできることは言うまでもありません。
-

          
-
*For Eachを使わない列挙
-
最後に、IEnumeratorとFor Eachの関係が理解できたので、IEnumeratorを取得してFor Eachだけを自作してみることにします。 IEnumeratorの挙動は、言い換えればFor Eachの挙動でもあるので、IEnumeratorの動作に従ってコーディングすればFor Eachを使わなくてもFor Eachに変わるものを作ることができます。 次のサンプルでは先ほどのIntegerArrayとIntegerArrayEnumeratorを用いてWhileステートメントで列挙を行っています。
 

        

        
+
:&msdn(netfx,id,9eekhta0){IEnumerable<T>インターフェイス};|IEnumerableインターフェイスを継承しているため、実装の際は以下のメンバに加え、IEnumerableのメンバも実装する必要がある。
+
::&msdn(netfx,id,s793z9y2){IEnumerable<T>.GetEnumeratorメソッド};|ジェネリックな列挙子であるIEnumerator<T>を返す必要がある点以外はIEnumerable.GetEnumeratorメソッドと同じ。
+
:&msdn(netfx,id,78dfe2yb){IEnumerator<T>インターフェイス};|IEnumeratorインターフェイスおよび&msdn(netfx,type,System.IDisposable){IDisposableインターフェイス};を継承しているため、実装の際は以下のメンバに加え、IEnumeratorのメンバも実装する必要がある。
+
::&msdn(netfx,id,58e146b7){IEnumerator<T>.Currentプロパティ};|現在の要素を、厳密に型定義された値として返すという点以外は、object型であるIEnumerator.Currentプロパティと同じ。
+
::&msdn(netfx,method,System.IDisposable.Dispose){IEnumerator<T>.Disposeメソッド};|IDisposableから継承されるメソッド。 列挙子が解放すべきリソースを保持する場合は、このメソッドで解放処理を実装する必要がある。 foreach文では、列挙操作が終了(または中断)する際に自動的に呼び出される。
+

          
+
IEnumerable<T>とIEnumerator<T>では、ジェネリック版に固有のメンバとIEnumerable・IEnumeratorから継承されるメンバの二種類を実装する必要がありますが、IEnumerable<T>・IEnumerator<T>の実装を流用することが出来るので、ほとんどの場合それぞれのメンバで別々の実装を二つ用意する必要はありません。 上記の例でも、非ジェネリックなメンバの実装に、ジェネリックなメンバの実装を流用しています。
+

          
+
また、IEnumerable<T>・IEnumerator<T>とIEnumerable・IEnumeratorで同じ名前のメンバを実装しなければなりませんが、C#では明示的なインターフェイスの実装、VBではプライベートな別名のメンバによる実装を用いることで、それぞれのインターフェイスのメンバを実装することが出来ます。 なお、IEnumerable<T>・IEnumerator<T>を実装している型の場合でも、列挙時の動作はDisposeメソッドが呼ばれる以外はIEnumerable・IEnumeratorの場合と同じです。
+

          
+
次の例は、for文/foreach文を使わずに配列を列挙する例です。 配列を一旦IEnumerable<T>にキャストして、GetEnumeratorでIEnumerator<T>を取得することで配列内の要素を列挙しています。 また、usingステートメントを使うことで、取得したIEnumerator<T>のDisposeメソッドを確実に呼び出すようにしています。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.Collections.Generic;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    int[] arr = new int[] {0, 1, 2};
+

          
+
    // IEnumerable<int>にキャスト
+
    IEnumerable<int> enumerable = arr;
+

          
+
    // IEnumerator<int>を取得
+
    using (IEnumerator<int> enumerator = enumerable.GetEnumerator())
+
    {
+
      int e;
+

          
+
      // MoveNextがfalseを返すまで繰り返す
+
      while (enumerator.MoveNext())
+
      {
+
        // 現在の値を取得
+
        e = (int)enumerator.Current;
+

          
+
        Console.WriteLine(e);
+
      }
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
+
Imports System.Collections.Generic
 

        

        
~
Class Sample
' アプリケーションのエントリーポイントのためのクラス
~
  Shared Sub Main()
Public NotInheritable Class Enumeration
+
    Dim arr() As Integer = New Integer() {0, 1, 2}
+

          
+
    ' IEnumerable(Of Integer)にキャスト
+
    Dim enumerable As IEnumerable(Of Integer) = arr
+

          
+
    ' IEnumerator(Of Integer)を取得
+
    Using enumerator As IEnumerator(Of Integer) = enumerable.GetEnumerator()
+
      Dim e As Integer
+

          
+
      ' MoveNextがFalseを返すまで繰り返す
+
      While enumerator.MoveNext()
+
        ' 現在の値を取得
+
        e = CInt(enumerator.Current)
+

          
+
        Console.WriteLine(e)
+
      End While
+
    End Using
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
    ' エントリーポイント
~
0
    Public Shared Function Main(ByVal args() As String) As Integer
+
1
+
2
+
}}
 

        

        
~
----
        Dim arr As New IntegerArray(5)
 

        

        
~
*IEnumerable・IEnumeratorを実装しない型の列挙
        Dim i As Integer
+
ここまでの解説では、列挙を行うためにはIEnumerableを実装する必要があるとしてきました。 しかし実際には、C#のforeach文、VBのFor EachステートメントではIEnumerableを実装していない型でも列挙操作を行うことが出来ます。 以下は、IEnumerable・IEnumeratorを実装していないクラスで列挙操作を行う例です。
 

        

        
~
#tabpage(C#)
        ' 配列に値を与える
~
#code(cs){{
        For i = 0 To arr.Length - 1
+
using System;
+

          
+
class Enumerable /* : IEnumerable */
+
{
+
  public Enumerator GetEnumerator()
+
  {
+
    return new Enumerator();
+
  }
+
}
+

          
+
class Enumerator /* : IEnumerator */
+
{
+
  int pos = -1;
+

          
+
  public bool MoveNext()
+
  {
+
    pos++;
+

          
+
    if (pos == 3)
+
    {
+
      return false;
+
    }
+
    else
+
    {
+
      return true;
+
    }
+
  }
+

          
+
  public int Current
+
  {
+
    get { return pos; }
+
  }
+

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

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Enumerable enumerable = new Enumerable();
+

          
+
    foreach (int e in enumerable)
+
    {
+
      Console.WriteLine(e);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
Class Enumerable
            arr(i) = i
+
  ' Implements IEnumerable
 

        

        
~
  Public Function GetEnumerator() As Enumerator
        Next
+
    Return New Enumerator()
+
  End Function
+
End Class
 

        

        
~
Class Enumerator
        ' While ステートメントで配列の値を表示する
~
  ' Implements IEnumerator
        Console.WriteLine("Enumerate by 'While' statement.")
 

        

        
~
  Dim pos As Integer = -1
        ' 列挙子を取得
-
        Dim enumerator As IEnumerator = arr.GetEnumerator()
 

        

        
~
  Public Function MoveNext() As Boolean
        ' 列挙子をリセット
~
    pos += 1
        enumerator.Reset()
 

        

        
~
    If pos = 3 Then
        ' Falseが返されるまでループ
~
      Return False
        While enumerator.MoveNext()
+
    Else
+
      Return True
+
    End If
+
  End Function
+

          
+
  Public ReadOnly Property Current() As Integer
+
    Get
+
      Return pos
+
    End Get
+
  End Property
+

          
+
  Public Sub Reset()
+
    Throw New NotSupportedException()
+
  End Sub
+
End Class
 

        

        
~
Class Sample
            ' 要素を取得
~
  Shared Sub Main()
            i = CType(enumerator.Current, Integer)
+
    Dim enumerable As New Enumerable()
+

          
+
    For Each e As Integer In enumerable
+
      Console.WriteLine(e)
+
    Next
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
            Console.WriteLine(i)
+
0
+
1
+
2
+
}}
 

        

        
~
このように、IEnumerable・IEnumeratorを実装していなくても、IEnumerable・IEnumeratorと同等のメンバを持つ型であればforeach文/For Eachステートメントで列挙を行うことは出来ます。 これらはいずれもコンパイラによりサポートされる機能です。 ただ、このようにして実装された型を他の言語で使用した場合でも列挙出来るとは限らないため、他の言語との相互運用性を考慮するとIEnumerable・IEnumeratorを用いて実装すべきです。
        End While
 

        

        
~
*イテレータ (反復子)
        Return 0
+
C# 2.0よりイテレータ構文が導入されています。 これはIEnumeratorの実装を大幅に簡略化できるもので、VBには用意されていない構文です。 yieldキーワードを用いることで、IEnumerator.MoveNextメソッドの呼び出しとIEnumerator.Currentプロパティの参照に相当する処理を簡単に記述することが出来ます。
 

        

        
~
以下の例のように、戻り値の型がIEnumerable(IEnumerable<T>)もしくはIEnumerator(IEnumerator<T>)であるメソッド・プロパティでイテレータ構文を記述することができます。 ただし、匿名メソッドでは使用できない、例外処理に制限があるなど、イテレータ構文特有の注意点があります。 詳細は&msdn(netfx,id,dscyy5s0){反復子 (C# プログラミング ガイド)};を参照してください。
    End Function
 

        

        
~
#code(cs){{
End Class
+
using System;
+
using System.Collections;
+
using System.Collections.Generic;
+

          
+
class Enumerable : IEnumerable
+
{
+
  public IEnumerator GetEnumerator()
+
  {
+
    yield return 0;
+
    yield return 1;
+
    yield return 2;
+
  }
+
}
+

          
+
class Sample
+
{
+
  static IEnumerable<string> Enumerate()
+
  {
+
    yield return "foo";
+
    yield return "bar";
+
    yield return "baz";
+
  }
+

          
+
  static void Main()
+
  {
+
    Console.WriteLine("yield IEnumerator");
+

          
+
    Enumerable enumerable = new Enumerable();
+

          
+
    foreach (int e in enumerable)
+
    {
+
      Console.WriteLine(e);
+
    }
+

          
+
    Console.WriteLine("yield IEnumerable<T>");
+

          
+
    foreach (string e in Enumerate())
+
    {
+
      Console.WriteLine(e);
+
    }
+
  }
+
}
 
}}
}}
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
yield IEnumerator
Enumerate by 'While' statement.
 
0
0
 
1
1
 
2
2
~
yield IEnumerable<T>
3
~
foo
4
~
bar
Press any key to continue
+
baz
 
}}
}}