ここでは非ジェネリックなコレクション型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