今回は、IEnumerable<T>インターフェースの基本について書きます。
IEnumerable<T>を説明するときにはIEnumerator<T>がついてきます。
この2つのインターフェースは関係があり、IEnumerable<T>のGetEnumerator()は戻り値がIEnumerator<T>型なのです。
IEnumerable<T>の説明
MSDNでの解説は、
「指定した型のコレクションに対する単純な反復処理をサポートする列挙子を公開します。」
となっています。
反復処理と列挙子
「反復処理」は、while 、 do 、 for 、 foreach などのキーワードによる処理のことです。
「サポートする列挙子」ですが、意外に「列挙子」についての説明は少ないものです。
「列挙子」で検索すると、enum(列挙型)のほうがよくヒットします。
本題に戻って、列挙子ですが、列挙は
「並べあげること。一つ一つ数えあげること」
です。「列挙子=列挙するもの」とすると
「並べあげるモノ。一つ一つ数えあげるモノ」
となります。
「コレクションに対して数えるモノを公開してるだけ?」と思われると思います。
はい、IEnumerable<T>、IEnumerator<T>インターフェースの役割はそれだけです。
IEnumerable<T>インターフェースにプロパティはありません。
IEnumerator<T>インターフェースには、Currentプロパティがあります。
object Current | コレクション内の現在の要素を取得します |
IEnumerable<T>、IEnumerator<T>インターフェースのメソッド
IEnumerable<T>インターフェースのメソッドですが、IEnumerator<T> GetEnumerator()1つしかありません。
IEnumerator GetEnumerator() | コレクションを反復処理する列挙子を返します。 |
IEnumerator<T>インターフェースのメソッドは2つあります。
bool MoveNext() | 列挙子をコレクションの次の要素に進めます。 戻り値がbool型で、正常に次の要素にうつった場合はtrue、最後の要素の次にいった場合はfalseを返します。 |
void Reset() | 列挙子を初期位置、つまりコレクションの最初の要素の前に設定します。 |
コレクションでIEnumeratorを継承する理由
コレクションというのは「ある型の項目の集まり」です。項目も少ないものから多いものまでいろいろあります。
プログラムでは、この「項目の集まり」に必ず”何か”します。
- 「項目の集まり」からある条件に合致するものを探し出す。
- 「項目の集まり」すべてを操作する。
など・・・こういった操作を行うときには、さきほど出てきた「反復処理」を使用します。
なのでこの「反復処理」を行うためのものとして「IEnumerable、IEnumerable<T>インターフェース」が存在しています。
(ちなみにC++では、反復子[イテレータ]があります)
逆にC#のコレクションなのに、このforeach文が使えないのはとても不便に感じます。
ジェネリックコレクションに限らず、自分で作ったコレクションクラスを公開する可能性がある場合は、このIEnumerable、IEnumerable<T>インターフェースを継承したほうがよいでしょう。
それは、「ジェネリックコレクションと同じようにforeach文などが書けるようになる」からです。
IEnumerator<T>、IEnumerable<T>インターフェースの実装
説明だけだと、とてもシンプルなイメージですが、実装はややこしい(ロジックなどではなく、ここまででもお分かりいただけたかと思いますが、この2つのインターフェースはスペルが非常によく似ています)
今回のサンプルでは、実際のプログラムなどではなくてもいいですが、List<string>を「項目の集まり」と見立てて、foreach文でアクセスするサンプルです。
/// <summary> /// IEnumerable<T>の派生クラス(コレクションもどき) /// </summary> public class StringList : IEnumerable<string> { /// <summary> /// 走査するリスト /// </summary> private List<string> _list; /// <summary> /// コンストラクタ /// </summary> /// <param name="list">走査するリスト</param> public StringList(List<string> list) { _list = list; } /// <summary> /// IEnumerator<T>の派生クラスオブジェクトを取得する /// </summary> /// <returns>IEnumerator<T>の派生クラスオブジェクト</returns> public IEnumerator<string> GetEnumerator() { return new StringEnumerator(_list); } /// <summary> /// System.Collections.IEnumeratorクラスオブジェクトを取得する /// </summary> /// <returns>System.Collections.IEnumerator<T>の派生クラスオブジェクト</returns> IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } /// <summary> /// IEnumerator<T>の派生クラス /// </summary> public class StringEnumerator : IEnumerator<string> { /// <summary> /// 現在の位置(インデックス) /// </summary> int _pos; /// <summary> /// 走査するリスト /// </summary> List<string> _list; /// <summary> /// 現在のオブジェクト /// </summary> public string Current { get { return _list[_pos]; } } /// <summary> /// System.Collections.IEnumerator.Currentプロパティ /// </summary> object System.Collections.IEnumerator.Current { get { return (object)this.Current; } } /// <summary> /// コンストラクタ /// </summary> /// <param name="list">走査するリスト</param> public StringEnumerator(List<string> list) { // _list = list; _pos = -1; } /// <summary> /// 次のオブジェクトを取得する /// </summary> /// <returns>処理結果</returns> public bool MoveNext() { _pos++; if(_pos >= _list.Count) { return false; } return true; } /// <summary> /// リセット処理(先頭の1つ前を指す) /// </summary> public void Reset() { _pos = -1; } /// <summary> /// オブジェクトを破棄するときの処理 /// </summary> void IDisposable.Dispose() { } } static void Main(string[] args) { System.Collections.Generic.List<string> lst = new List<string>(); lst.Add("AAA"); lst.Add("BB"); lst.Add("CCC"); StringList sl = new GenericTest1.StringList(lst); foreach(string s in sl) { Console.WriteLine(s); } }
今回は、StringListクラスは、IEnumerable<string>の説明のため、IEnumerable<string>のみの派生クラスとして作成しましたが、StringListクラスがICollection<string>も継承しているとコレクションクラスになります。