インターフェイスの恩恵を最も身近に感じることができるのは、コレクション・オブジェクトの各要素を順番に列挙できるforeachステートメント(C#)、For Each...Nextステートメント(VB.NET)(以下、「foreach文」と略)でしょう。
foreach文は次のようにして利用できます。このコードは、複数のコントロールを含むコレクション・オブジェクト(フォームのControlsプロパティ)から、その要素である各コントロールを順番に1つずつ取り出して何らかの処理をするものです*5。
*5 このコードではフォームの直接の子コントロールしか列挙できません。すべてのコントロールを列挙する方法については「.NET TIPS:Windowsフォーム上のすべてのコントロールを列挙するには?」を参照してください。
ちなみに、もしforeach文が言語に用意されていないとすると、上記のコードはforステートメント(C#)、For...Nextステートメント(VB.NET)を使って、次のように記述しなければなりません。
コレクション・オブジェクトの全要素に対して順番に何かの処理を行うという作業は頻繁に行われるため、C#やVB.NETにはforeach文が用意されたものと思われます。
当然ながらforeach文で列挙可能なオブジェクトは、どのようなオブジェクトでもOKということはありません。foreach文が要素を順に取り出すことのできるオブジェクトは、それがコレクション・オブジェクトでなければなりません。
では、ここでいうforeach可能なコレクション・オブジェクトとは何によって決まるのかというと、それはIEnumerableインターフェイスを実装しているオブジェクトとなります。ちなみに、enumerateは「列挙する」、enumerableは「列挙可能な」という意味です。
IEnumerableインターフェイスの定義は次のようになっています。これにはメソッドが1つだけ定義されています。
namespace System.Collections
{
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
}
foreach可能なすべてのオブジェクトは、このGetEnumeratorメソッドを実装していることになります。そして、このメソッドは「列挙用オブジェクト」を返します。
ここで列挙用オブジェクトと呼んでいるオブジェクトは、コレクション・オブジェクトの要素を1つずつ順番に取り出すことのできるオブジェクトです。これらの関係を次の図に示します。
コレクション・オブジェクトからGetEnumeratorメソッドにより得られる列挙用オブジェクトは、コレクション・オブジェクトが管理しているコレクション内の1要素を指すインデックス番号を保持しています。MoveNextメソッドはこのインデックス番号を1つ進めるためのものです。また、Currentプロパティは現在インデックス番号が指している要素をコレクションから取り出して返します。列挙用オブジェクトは、このようにしてコレクションの要素を順番に取り出すことができます。
foreach文は、このような列挙用オブジェクトを利用して、コレクション・オブジェクトの要素を1つずつ取り出すことができます。
それでは、foreach文はどのようにして列挙用オブジェクトを利用するのでしょうか。ここで、先ほどのforeach文をもう一度示しておきます。
このようなコードは、実はC#やVB.NETのコンパイラにより、コンパイル時に次のようなコードに展開されるのです*6。
*6 より正確には、GetEnumeratorメソッドで取得したIEnumeratorオブジェクトがIDisposableインターフェイスを実装している場合に、例外処理としてそのDisposeメソッドを呼び出すというコードも入りますが、ここでは省略しています。
1行目のIEnumerator型の変数「e」が列挙用オブジェクトと呼んでいるものです(IEnumeratorについては次項で説明します)。MoveNextメソッドは呼び出されるたびに内部で保持しているインデックス番号を1つ進め、通常は戻り値としてtrueを返しますが、コレクションの最後の要素に達するとfalseを返します。
C#やVB.NETのコンパイラは、foreach文で指定されたコレクション・オブジェクトがGetEnumeratorメソッドを定義しているか、そのメソッドの戻り値である型のクラスがMoveNextメソッドやCurrentプロパティを定義しているかをコンパイル時にチェックして、foreach文をこのようなコードに展開します*7。
*7 インターフェイスの実装をチェックするわけではないようです。このため、実際にはこれらのメソッドを準備しておけばインターフェイスを実装しなくてもforeach文により列挙可能なクラスは実装可能です。これについてはリファレンス・マニュアルのforeach のコレクションでの使い方などに記述されています。
次にGetEnumeratorメソッドの戻り値の型であるIEnumerator型についても説明しておきましょう。すでにお分かりのように、「IEnumerator」はインターフェイスの名前です。これまで列挙用オブジェクトと呼んでいたオブジェクトは、このIEnumeratorインターフェイスを実装しているオブジェクトということになります。
IEnumeratorインターフェイスの定義を次に示します。
namespace System.Collections
{
public interface IEnumerator
{
// メソッド
bool MoveNext();
void Reset();
// プロパティ
object Current { get; }
}
}
IEnumerableとIEnumeratorというよく似た名前のインターフェイスが登場するので混乱しないようにしてください。IEnumerableインターフェイスはコレクション・オブジェクトを列挙可能にするためのもの、IEnumeratorインターフェイスは、そのオブジェクトを列挙用オブジェクトにするためのものです。
コンパイラがforeach文を展開したコードの最初の1行は次のようになっていました。
IEnumerator e = Form1.Controls.GetEnumerator();
GetEnumeratorメソッドが返すものは、実際には何らかのクラスのオブジェクトですが、このコードでは、その具体的なクラスには関係なく「IEnumeratorインターフェイスを実装したオブジェクト」として、そのオブジェクトを扱います。そしてポリモーフィズムにより、どのようなオブジェクトであっても「e.MoveNext()」や「e.Current」が実行可能となるわけです。
Copyright© Digital Advantage Corp. All Rights Reserved.