ObservableCollection

System.Collections.ObjectModel.ObservableCollectionクラスは、コレクションに対する挿入・削除・設定を行った際にそれらの変更をクラス外部にイベントとして通知することができるようになっているクラスです。 ObservableCollectionクラスは、ユーザーインターフェイスとコレクションの同期したい場合などに使用されます。

ObservableCollectionクラス自体はCollectionクラスを継承しているため基本的な操作はCollectionクラスと同じですが、要素の移動を行うMoveメソッドが追加されています。 クラス外からは、CollectionChangedイベント(NotifyCollectionChangedEventHandler)に適切なハンドラを設定することで、コレクションに対する変更を検出できます。

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

class Sample {
  static void Main()
  {
    ObservableCollection<int> col = new ObservableCollection<int>();

    // コレクション変更のイベントを受け取るハンドラを設定
    col.CollectionChanged += PrintCollectionChanged;

    // 要素の追加
    col.Add(0);
    col.Add(1);
    col.Add(2);
    col.Add(3);
    col.Add(4);

    Print(col);

    // 要素の削除
    col.Remove(1);
    col.Remove(3);

    Print(col);

    // 特定の位置にある要素を削除
    col.RemoveAt(1);

    Print(col);

    // 特定の位置にある要素を変更
    col[0] = 5;

    Print(col);

    // 特定の位置に要素を挿入
    col.Insert(1, 6);

    Print(col);

    // 特定の位置にある要素を別の位置に移動
    col.Move(2, 0); // インデックス2の要素をインデックス0に移動

    Print(col);
  }

  static void PrintCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  {
    Console.WriteLine(e.Action);
  }

  static void Print(ObservableCollection<int> col)
  {
    foreach (int e in col) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
Imports System
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized

Class Sample
  Shared Sub Main()
    Dim col As New ObservableCollection(Of Integer)()

    ' コレクション変更のイベントを受け取るハンドラを設定
    AddHandler col.CollectionChanged, AddressOf PrintCollectionChanged

    ' 要素の追加
    col.Add(0)
    col.Add(1)
    col.Add(2)
    col.Add(3)
    col.Add(4)

    Print(col)

    ' 要素の削除
    col.Remove(1)
    col.Remove(3)

    Print(col)

    ' 特定の位置にある要素を削除
    col.RemoveAt(1)

    Print(col)

    ' 特定の位置にある要素を変更
    col(0) = 5

    Print(col)

    ' 特定の位置に要素を挿入
    col.Insert(1, 6)

    Print(col)

    ' 特定の位置にある要素を別の位置に移動
    col.Move(2, 0) ' インデックス2の要素をインデックス0に移動

    Print(col)
  End Sub

  Shared Sub PrintCollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
    Console.WriteLine(e.Action)
  End Sub

  Shared Sub Print(col As ObservableCollection(Of Integer))
    For Each e As Integer In col
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果
Add
Add
Add
Add
Add
0, 1, 2, 3, 4, 
Remove
Remove
0, 2, 4, 
Remove
0, 4, 
Replace
5, 4, 
Add
5, 6, 4, 
Move
4, 5, 6, 

派生クラスからは、Itemsプロパティを参照することで内部コレクションにアクセスすることが出来ます。 また、SetItem, InsertItem, RemoveItem, ClearItemに加え、MoveItemの各メソッドをオーバーライドすることで要素の挿入・削除・設定・移動時の動作を拡張できます。 これらのメソッドをオーバーライドしない場合、既定の動作としてOnCollectionChangedメソッドが呼び出され、CollectionChangedイベントが発生します。

また、ObservableCollectionクラスはINotifyPropertyChangedインターフェイスも実装しています。 派生クラスでOnPropertyChangedメソッドを呼び出すようにすると、PropertyChangedイベントを発生させることが出来るようになります。

INotifyPropertyChangedインターフェイスについてはプロパティ §.プロパティ変更の通知 (INotifyPropertyChanged)を参照してください。

(未整理)
BlockReentrancyメソッドCheckReentrancyメソッドを使うことで、コレクション変更時の再入試行を禁止できるようになります。 BlockReentrancyメソッドが返すIDisposableオブジェクトが破棄される前にCheckReentrancyを呼び出すと例外InvalidOperationExceptionをスローします。

並べ替え(ソート・リバース)

ObservableCollectionにはSortやReverseといった並べ替えのメソッドは用意されていませんが、拡張メソッドのOrderByメソッドOrderByDescendingメソッドを使うことでコレクションをソートした結果を得ることが出来ます。

もしくは、Collectionと同様にObservableCollectionを継承して内部のコレクションをソート・リバースするメソッドを追加することが出来ます。 なお、このようにして追加したメソッドを呼び出す場合、ソート・リバースを行い要素が並べ替えられる度にCollectionChangedイベントが発生する点に注意が必要です。

次の例は、ObservableCollectionを継承してSortメソッドとReverseメソッドを実装するものです。

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

class SortableCollection : ObservableCollection<int> {
  public void Sort()
  {
    // IListインターフェイスからArrayListのラッパーを作り、既定のIComparer<int>を使ってソートする
    System.Collections.ArrayList.Adapter(this).Sort(System.Collections.Generic.Comparer<int>.Default);
  }

  public void Reverse()
  {
    // IListインターフェイスからArrayListのラッパーを作りリバースする
    System.Collections.ArrayList.Adapter(this).Reverse();
  }
}

class Sample {
  static void Main()
  {
    SortableCollection col = new SortableCollection();

    // コレクション変更のイベントを受け取るハンドラを設定
    col.CollectionChanged += PrintCollectionChanged;

    // 要素の追加
    col.Add(3);
    col.Add(1);
    col.Add(0);
    col.Add(2);
    col.Add(4);

    // ソート
    Console.WriteLine("[Sort]");

    col.Sort();

    Print(col);

    // リバース
    Console.WriteLine("[Reverse]");

    col.Reverse();

    Print(col);
  }

  static void PrintCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  {
    Console.WriteLine(e.Action);
  }

  static void Print(SortableCollection col)
  {
    foreach (int e in col) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
Imports System
Imports System.Collections.ObjectModel

Class SortableCollection
  Inherits ObservableCollection(Of Integer)

  Public Sub Sort()
    ' IListインターフェイスからArrayListのラッパーを作り、既定のIComparer<int>を使ってソートする
    System.Collections.ArrayList.Adapter(Me).Sort(System.Collections.Generic.Comparer(Of Integer).Default)
  End Sub

  Public Sub Reverse()
    ' IListインターフェイスからArrayListのラッパーを作りリバースする
    System.Collections.ArrayList.Adapter(Me).Reverse()
  End Sub
End Class

Class Sample
  Shared Sub Main()
    Dim col As New SortableCollection()

    ' コレクション変更のイベントを受け取るハンドラを設定
    AddHandler col.CollectionChanged, AddressOf PrintCollectionChanged

    ' 要素の追加
    col.Add(3)
    col.Add(1)
    col.Add(0)
    col.Add(2)
    col.Add(4)

    ' ソート
    Console.WriteLine("[Sort]")

    col.Sort()

    Print(col)

    ' リバース
    Console.WriteLine("[Reverse]")

    col.Reverse()

    Print(col)
  End Sub

  Shared Sub PrintCollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
    Console.WriteLine(e.Action)
  End Sub

  Shared Sub Print(ByVal col As SortableCollection)
    For Each e As Integer In col
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果
Add
Add
Add
Add
Add
[Sort]
Replace
Replace
Replace
Replace
Replace
Replace
0, 1, 2, 3, 4, 
[Reverse]
Replace
Replace
Replace
Replace
4, 3, 2, 1, 0, 

ReadOnlyObservableCollection

System.Collections.ObjectModel.ReadOnlyObservableCollectionクラスは名前の通り読み取り専用のObservableCollectionとして動作するコレクションクラスです。 ReadOnlyObservableCollectionクラスは、基になるObservableCollectionをコンストラクタで指定する必要があり、このObservableCollectionに対する読み取り操作のみを許可するラッパーとして動作します。 ObservableCollectionクラスがCollectionクラスを継承しているのに対し、ReadOnlyObservableCollectionクラスはReadOnlyCollectionクラスを継承しています。 そのため、基本的な操作はReadOnlyCollectionと同じです。

ReadOnlyObservableCollectionクラスではCollectionChangedイベントはprotectedなイベントとなっているため、ハンドラを設定することは出来ません。 また、ReadOnlyCollectionと同様、基になるObservableCollectionへの変更はそれをラップするReadOnlyObservableCollectionにも反映されます。

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

class Sample {
  static void Main()
  {
    ObservableCollection<int> baseCol = new ObservableCollection<int>();
    ReadOnlyObservableCollection<int> col = new ReadOnlyObservableCollection<int>(baseCol);

    // コレクション変更のイベントを受け取るハンドラを設定
    baseCol.CollectionChanged += PrintCollectionChanged;

    baseCol.Add(0);
    baseCol.Add(1);
    baseCol.Add(2);

    Console.WriteLine("Contains 1: {0}", col.Contains(1));

    baseCol.Remove(1);

    Console.WriteLine("Contains 1: {0}", col.Contains(1));
  }

  static void PrintCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  {
    Console.WriteLine(e.Action);
  }
}
Imports System
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized

Class Sample
  Shared Sub Main()
    Dim baseCol As New ObservableCollection(Of Integer)()
    Dim col As New ReadOnlyObservableCollection(Of Integer)(baseCol)

    ' コレクション変更のイベントを受け取るハンドラを設定
    AddHandler baseCol.CollectionChanged, AddressOf PrintCollectionChanged

    baseCol.Add(0)
    baseCol.Add(1)
    baseCol.Add(2)

    Console.WriteLine("Contains 1: {0}", col.Contains(1))

    baseCol.Remove(1)

    Console.WriteLine("Contains 1: {0}", col.Contains(1))
  End Sub

  Shared Sub PrintCollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
    Console.WriteLine(e.Action)
  End Sub
End Class
実行結果
Add
Add
Add
Contains 1: True
Remove
Contains 1: False