IntegerArrayクラスはふつうのInteger型の配列と同様の振る舞いをします。 しかし、エラーによりFor Eachはできません。 そのエラーを見ると次のようになっています。
つまり、In 以降で指定すべきオブジェクトはコレクション型でなければならず、arrはコレクション型ではないことになります。
2.IEnumerableの実装
ヘルプでFor Eachステートメントについて調べると次のようなことが書いてありました。
For Each...Next ステートメントは、式の要素に基づいてループを実行します。For Each ステートメントの式によって返される値の型は、下で定義する "コレクション型" である必要があります。また、コレクションの要素型から繰り返し変数の型への、暗黙の型変換が必要です。
System.IEnumerable を実装するか、または次の条件をすべて満たしている場合、型 C はコレクション型になります。
・C に、シグネチャが GetEnumerator() で、型 E. を返す Public インスタンス メソッドが含まれる。
・E に、シグネチャが MoveNext() で、戻り値の型が Boolean である Public インスタンス メソッドが含まれる。
・E に、取得関数を持つ Current という名前の Public インスタンス プロパティが含まれる。このプロパティの型は、コレクション型の要素型と呼ばれます。
つまり簡単にまとめてしまうと、For Eachを使えるようにするにはその型がコレクション型である必要があり、コレクション型にするためにはIEnumerableインターフェイスを実装していればよい、といっていることになります。 それでは早速、先ほどのIntegerArrayクラスにIEnumerableインターフェイスを実装してみます。
3.IEnumeratorの正体
IEnumerable.GetEnumerator()メソッドは列挙子を取得するためのものと説明しましたが、その戻り値である列挙子はIEnumerator型です。 IEnumerableと名前が似ているので注意して下さい。 IEnumeratorはその名のとおり列挙を行うためのもので、これを取得することができればFor Eachする事ができるとしましたが、IEnumeratorの正体が分からないまま使っていても気分が悪いので、その実体を調べてみたいと思います。
IEnumeratorを実装するに当たり、実装するクラスは次の三つのメンバを実装しなければなりません。
1.列挙子を初期状態に設定するメソッド/シグニチャ: Public Sub Reset()
列挙子をコレクションの最初の要素の前の位置に設定する。
2.列挙子を次の要素に移動するメソッド/シグニチャ: Public Function MoveNext() As Boolean
列挙子をコレクションの次の要素に進め、コレクションの範囲を超えた場合はFalseを返す。
3.現在列挙子が指し示すコレクションの要素を取得する読み取り専用プロパティ/シグニチャ: 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クラスの中で宣言しています。