C# でカスタム(自作)のコレクションクラスを実装する方法を試してみます。
.NET Framework の基本クラス ライブラリは、System.Collections.ICollection インターフェイスというクラスを用いて作成します。
ただ、カスタムのコレクションクラスを作るには、コレクションクラスそのもの以外にも必要なものがあります。
まずは準備です。
コレクションで管理するクラスの作成
実際にコレクションに追加・アクセス・取得などを行うためのクラスを作成します。
今回は、Nameプロパティ1つしかないとても単純なクラスにしておきます。
public class ItemObject { /// <summary> /// 当オブジェクトの名前 /// </summary> public string Name; /// <summary> /// コンストラクタ /// </summary> public ItemObject() { // コンストラクタ } /// <summary> /// コンストラクタ /// </summary> /// <param name="name">当オブジェクトの名前</param> public ItemObject(string name) { // コンストラクタ this.Name = name; } }
カスタムのコレクションクラス本体の作成
コレクションクラス本体を作成しますが、ここがコレクションクラスを自作するときに悩ませてくれる部分です。
ICollectionインターフェースクラスの派生クラスとしてコレクションクラスを作成すると、下記のものを実装する必要があります。
- int ICollection.Countプロパティ
- object ICollection.SyncRootプロパティ
- bool ICollection.IsSynchronizedプロパティ
- void ICollection.CopyTo(Array list, int index)メソッド
- IEnumerator IEnumerable.GetEnumerator()メソッド
が必要となります(インターフェースクラスなのでこれは必須です)。
この中で一番困るのが”IEnumerator IEnumerable.GetEnumerator()メソッド”です。
最初は、「ICollectionの派生クラスは作るんだけど、この”IEnumerator”とか”IEnumerable”ってなんだ?」となります。
ですが、これはコレクションを使う上でとても大事なものなんです。
MSDNの説明には、
IEnumerableインターフェース | 非ジェネリック コレクションに対する単純な反復処理をサポートする列挙子を公開します。 |
IEnumerator インターフェイス | 非ジェネリック コレクションに対する単純な反復処理をサポートするためのインターフェース。 |
とあります。「単純な反復処理」って何?と思うかもしれませんが、一番見慣れている反復処理は「foreach文」があります。
IEnumerableクラスは、実はICollectionクラスの基本クラスなんです。
ですのでICollectionクラスを継承するクラスは、”IEnumerator IEnumerable.GetEnumerator()”も実装しなければいけません。
ここでさらに1つ疑問がおきます。
IEnumerable.GetEnumerator()の戻り値”IEnumerator”って何を返せばいいの?
IEnumeratorはインターフェースクラスです。
インターフェースは提供しますが、そのままのクラスでインスタンスを生成したりはできません。
ICollectionを派生してコレクションを作るときには、もれなくこの「“IEnumeratorクラス”の派生クラス」も作らなくてはいけないんです。
IEnumeratorインターフェースの派生クラスを作る
IEnumeratorインターフェースの派生クラスでは
- object IEnumerator.Currentプロパティ
- void IEnumerator.Reset()メソッド
- bool IEnumerator.MoveNext()メソッド
を実装しなくてはいけません。
IEnumeratorインターフェースの派生クラスとして”Enumerator”クラスを実装すると下記のようになります。
public class Enumerator : IEnumerator { /// <summary> /// 走査対象になるリスト /// </summary> private ArrayList _list = new ArrayList(); /// <summary> /// 現在のアクセス位置(インデックス) /// </summary> private int _cursor; /// <summary> /// 列挙子の現在の位置 /// </summary> object IEnumerator.Current { get { if ((_cursor < 0) || (_cursor == _list.Count)) { throw new InvalidOperationException(); } return _list[_cursor]; } } /// <summary> /// コンストラクタ /// </summary> public Enumerator() { // コンストラクタ } /// <summary> /// コンストラクタ /// </summary> /// <param name="list">走査対象のリスト</param> public Enumerator(ArrayList list) { // this._cursor = -1; this._list = list; } /// <summary> /// 列挙子リセット /// </summary> void IEnumerator.Reset() { this._cursor = -1; } /// <summary> /// 列挙子をコレクションの次の要素に進める /// </summary> /// <returns> /// 列挙子が次の要素に正常に進んだ場合は true。 /// 列挙子がコレクションの末尾を越えた場合は false /// </returns> bool IEnumerator.MoveNext() { if (this._cursor < _list.Count) { // 列挙子を1つ進める this._cursor++; } return (!(this._cursor == this._list.Count)); } }
次回は、これらのクラスを使ってコレクションクラスを実装して、動かしてみます。