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

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クラスでは提供されないため、派生クラスにて内部コレクションへの追加・削除・挿入を行うメソッドを用意する必要があります。

実装例

以下は、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);
    }
  }
}
Imports System
Imports System.Collections

Class StringCollection
  Inherits CollectionBase

  Public Sub Add(ByVal s As String)
    ' 型チェックの必要は無いので、直接内部コレクションに追加する
    InnerList.Add(s)
  End Sub

  Protected Overrides Sub OnValidate(ByVal val As Object)
    If (Not val Is Nothing) AndAlso (Not val.GetType() Is GetType(String)) Then
      ' valの型がstring以外の場合、ArgumentExceptionをスローする
      Throw New ArgumentException("String型以外はコレクションに追加できません")
    End If
  End Sub
End Class

Class Sample
  Shared Sub Main()
    Dim sc As New StringCollection()

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

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

    l.Add("bar")

    For Each s As String In sc
      Console.WriteLine(s)
    Next

    Try
      ' IList.AddメソッドはObjectを引数にとるので数値を追加しようとしても
      ' コンパイルエラーにはならないが、追加の際にOnValidateメソッドが
      ' 呼び出されるため、ArgumentExceptionがスローされる
      l.Add(16)
    Catch ex As ArgumentException
      Console.WriteLine(ex.Message)
    End Try
  End Sub
End Class
実行結果
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());
    }
  }
}
Imports System
Imports System.Collections

Class IntStack
  Inherits CollectionBase

  Public Sub Push(ByVal i As Integer)
    InnerList.Insert(InnerList.Count, i)
  End Sub

  Public Function Pop() As Integer
    Dim headIndex As Integer = InnerList.Count - 1

    If headIndex = -1 Then Throw New InvalidOperationException("IntStackは空です")

    Dim val As Integer = CInt(InnerList(headIndex))

    InnerList.RemoveAt(headIndex)

    Return val
  End Function

  Public Function Peek() As Integer
    Dim headIndex As Integer = InnerList.Count - 1

    If headIndex = -1 Then Throw New InvalidOperationException("IntStackは空です")

    Return CInt(InnerList(headIndex))
  End Function

  Protected Overrides Sub OnValidate(ByVal val As Object)
    If (Not val Is Nothing) AndAlso (Not val.GetType() Is GetType(Integer)) Then
      ' valの型がInteger以外の場合、ArgumentExceptionをスローする
      Throw New ArgumentException("Integer型以外はコレクションに追加できません")
    End If
  End Sub
End Class

Class Sample
  Shared Sub Main()
    Dim s As New IntStack()
    'Dim s As 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())
    End While
  End Sub
End Class
実行結果
Peek: 4
Pop: 4
Pop: 3
6
5
2
1
0