ここでは非ジェネリックなコレクション型System.Collections.CollectionBaseクラスについて解説します。 CollectionBaseクラスを使用するよりも、CollectionBaseに相当するジェネリックなコレクション型System.Collections.ObjectModel.Collection<T>クラスを使用することを強く推奨します。

§1 CollectionBase

System.Collections.CollectionBaseクラスは、独自にコレクションクラスを実装するのに便利な抽象クラスです。 コレクションに要素を追加・挿入・削除・設定する際の動作をオーバーライドすることができます。

CollectionBaseクラスを継承して、挿入される要素の型をチェックするようにすればジェネリックコレクションのように厳密に型指定されたコレクションを作ることが可能です。 また、コレクションの内容が変更されたときにイベントを発生させてクラス外に通知する、といったことも可能になります。

CollectionBaseクラスでは内部にArrayListを持っていて、これがコレクションとしての実体となります。 派生クラスからは、Listプロパティを通してアクセスします。 また、CollectionBaseクラスでは、次のプロテクトメソッドをオーバーライドすることが出来ます。

オーバーライド可能なメソッド
メソッド メソッドが呼ばれるタイミング
OnValidate 内部コレクションに要素を追加・設定しようとしたとき
OnSet, OnInsert, OnRemoveの各メソッドが呼ばれる前に呼び出されます
OnSet 内部コレクションの要素を設定しようとしたとき
OnInsert 内部コレクションに要素を挿入しようとしたとき
OnRemove 内部コレクションから要素を削除しようとしたとき
OnClear 内部コレクションから全要素を削除しようとしたとき
OnSetComplete 内部コレクションへの要素の設定が完了したとき
OnInsertComplete 内部コレクションへの要素の挿入が完了したとき
OnRemoveComplete 内部コレクションからの要素の削除が完了したとき
OnClearComplete 内部コレクションからの全要素の削除が完了したとき

これらのメソッドは、派生クラスからListプロパティを通して内部コレクションにアクセスした場合と、クラス外からIListインターフェイスを経由してアクセスされた場合に呼び出されます。 派生クラスから内部コレクションにアクセスする方法としてもう一つInnerListプロパティも用意されていますが、このプロパティを通して直接内部コレクションの実体にアクセスした場合は、これらのメソッドは呼び出されません。

また、Add, Insert, Remove, ContainsなどのメソッドはCollectionBaseクラスでは提供されないため、派生クラスにて内部コレクションへの追加・削除・挿入を行うメソッドを用意する必要があります。

§1.1 実装例

以下は、CollectionBaseを継承し、OnValidateメソッドをオーバーライドしてString型の値のみをコレクションに追加できるStringCollectionクラスを実装する例です。

using System;
using System.Collections;

class StringCollection : CollectionBase {
  public void Add(string s)
  {
    // 型チェックの必要は無いので、直接内部コレクションに追加する
    InnerList.Add(s);
  }

  protected override void OnValidate(object val)
  {
    if (val != null && val.GetType() != typeof(string))
      // valの型がstring以外の場合、ArgumentExceptionをスローする
      throw new ArgumentException("String型以外はコレクションに追加できません");
  }
}

class Sample {
  static void Main()
  {
    StringCollection sc = new StringCollection();

    // 独自に実装したAddメソッドで要素を追加
    sc.Add("foo");

    // IListインターフェイスに変換し、IList.Addメソッドで要素を追加
    IList l = sc;

    l.Add("bar");

    foreach (string s in sc) {
      Console.WriteLine(s);
    }

    try {
      // IList.Addメソッドはobjectを引数にとるので数値を追加しようとしても
      // コンパイルエラーにはならないが、追加の際にOnValidateメソッドが
      // 呼び出されるため、ArgumentExceptionがスローされる
      l.Add(16);
    }
    catch (ArgumentException ex) {
      Console.WriteLine(ex.Message);
    }
  }
}
実行結果
foo
bar
String型以外はコレクションに追加できません

以下は、CollectionBaseを継承し、Integer型の値のみを追加できるスタックIntStackを実装する例です。

using System;
using System.Collections;

class IntStack : CollectionBase {
  public void Push(int i)
  {
    InnerList.Insert(InnerList.Count, i);
  }

  public int Pop()
  {
    int headIndex = InnerList.Count - 1;

    if (headIndex == -1) throw new InvalidOperationException("IntStackは空です");

    int val = (int)InnerList[headIndex];

    InnerList.RemoveAt(headIndex);

    return val;
  }

  public int Peek()
  {
    int headIndex = InnerList.Count - 1;

    if (headIndex == -1) throw new InvalidOperationException("IntStackは空です");

    return (int)InnerList[headIndex];
  }

  protected override void OnValidate(object val)
  {
    if (val != null && val.GetType() != typeof(int))
      // valの型がint以外の場合、ArgumentExceptionをスローする
      throw new ArgumentException("Integer型以外はコレクションに追加できません");
  }
}

class Sample {
  static void Main()
  {
    IntStack s = new IntStack();
    //Stack s = new Stack();

    s.Push(0);
    s.Push(1);
    s.Push(2);
    s.Push(3);
    s.Push(4);

    // Stackの先頭にある要素をPeek
    Console.WriteLine("Peek: {0}", s.Peek());

    // Stackの先頭にある要素をPop
    Console.WriteLine("Pop: {0}", s.Pop());
    Console.WriteLine("Pop: {0}", s.Pop());

    s.Push(5);
    s.Push(6);

    // Stackが空になるまで内容をPop
    while (0 < s.Count) {
      Console.WriteLine(s.Pop());
    }
  }
}
実行結果
Peek: 4
Pop: 4
Pop: 3
6
5
2
1
0