System.Collections.ObjectModel.ObservableCollection<T> は、要素の追加・削除・変更を外部に通知する 機能を持ったコレクションです。WPF、MAUI、Avalonia などのデータバインディングフレームワークと組み合わせ、コレクションの変更が UI に自動反映される MVVM パターンの主役として使われます。
ObservableCollection<T> とは
- 名前空間:
System.Collections.ObjectModel - 基底クラス:
Collection<T>(List<T>を内部に持つラッパ) - 要素変更時に
CollectionChangedイベントを発火 Countなどのプロパティ変更時にPropertyChangedイベントを発火- 内部構造は
List<T>と同じ動的配列
using System.Collections.ObjectModel;
using System.Collections.Specialized;
var items = new ObservableCollection<string> { "apple", "banana" };
items.CollectionChanged += (s, e) =>
{
Console.WriteLine($"Action: {e.Action}");
if (e.NewItems != null)
foreach (var x in e.NewItems) Console.WriteLine($" + {x}");
if (e.OldItems != null)
foreach (var x in e.OldItems) Console.WriteLine($" - {x}");
};
items.Add("cherry");
// Action: Add
// + cherry
items.Remove("apple");
// Action: Remove
// - apple
items[0] = "blueberry";
// Action: Replace
// - banana
// + blueberry
items.Move(0, 1);
// Action: Move
サポートするインターフェース
| インターフェース | 役割 |
|---|---|
IList<T> |
インデックスアクセス・挿入・削除を持つ順序付きコレクション |
IReadOnlyList<T> |
読み取り専用ビュー |
ICollection<T> |
Count / Add / Remove 等 |
IReadOnlyCollection<T> |
読み取り専用 Count |
IEnumerable<T> |
foreach での列挙 |
IList / ICollection / IEnumerable |
非ジェネリック互換 |
INotifyCollectionChanged |
コレクション変更通知 |
INotifyPropertyChanged |
プロパティ変更通知(Count、Item[]) |
IList<T> などの基本コレクション系インターフェースの詳細は ListIEnumerable は IEnumerable と IEnumerator の記事 を参照してください。
INotifyCollectionChanged
ObservableCollection<T> の中核となる、コレクションの内容変更を通知 するためのインターフェース。
public interface INotifyCollectionChanged
{
event NotifyCollectionChangedEventHandler? CollectionChanged;
}
イベント引数 NotifyCollectionChangedEventArgs には Action(変更種別)、OldItems、NewItems、OldStartingIndex、NewStartingIndex が含まれます。
Action は NotifyCollectionChangedAction 列挙体:
| 値 | 意味 |
|---|---|
Add |
新規要素が追加された |
Remove |
要素が削除された |
Replace |
インデクサ代入で要素が置換された |
Move |
Move(oldIndex, newIndex) で並び替えられた |
Reset |
Clear で全削除された |
INotifyPropertyChanged
Count プロパティや Item[](インデクサ)が変わったことを通知。WPF などの UI が Count をテキストバインドしているケースで効きます。
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler? PropertyChanged;
}
主な API と計算量
性能特性は List<T> とほぼ同じ(内部に List<T> を持つため)。加えて変更時にイベント発火のオーバーヘッドが乗ります。
| 操作 | API | 計算量 | 備考 |
|---|---|---|---|
| インデックスアクセス | [i] |
O(1) | 通知なし(ゲッタは) |
| 末尾追加 | Add(item) |
O(1) 償却 | Add 通知 |
| 任意位置挿入 | Insert(i, item) |
O(n) | Add 通知 |
| 削除 | Remove(item) / RemoveAt(i) |
O(n) | Remove 通知 |
| 置換 | [i] = item |
O(1) | Replace 通知 |
| 並び替え | Move(oldIndex, newIndex) |
O(n) | Move 通知 |
| 全削除 | Clear() |
O(n) | Reset 通知 |
イベントハンドラの処理が重いと UI が固まるので、ハンドラ側の処理は軽く することが鉄則。
WPF / MAUI / Avalonia でのバインディング
XAML 側で ItemsControl.ItemsSource などにバインドすると、コレクションの変更が 自動的に UI に反映 されます。
<ListBox ItemsSource="{Binding Items}" />
public class MainViewModel
{
public ObservableCollection<string> Items { get; } = new();
}
Items.Add(...) するだけでリストの末尾に新しい行が追加されます。List<T> ではこの自動更新が効きません(変更通知を発火する仕組みがない)。
UI スレッド制約
WPF/MAUI などの UI フレームワークは、UI スレッドからのみ ObservableCollection を変更する ことを期待します。バックグラウンドスレッドから Add を呼ぶと NotSupportedException や InvalidOperationException が発生することがあります。
対処は次のいずれか。
-
UI スレッドにマーシャル
// WPF Application.Current.Dispatcher.Invoke(() => items.Add(x)); // MAUI MainThread.BeginInvokeOnMainThread(() => items.Add(x)); -
BindingOperations.EnableCollectionSynchronization(WPF 専用)var sync = new object(); BindingOperations.EnableCollectionSynchronization(items, sync); // 以降、別スレッドからでも lock(sync) 内で変更すれば OK -
専用ライブラリの活用:CommunityToolkit.Mvvm の
ObservableCollection拡張や、各種の thread-safe な observable コレクション。
List<T> との比較
| 観点 | List<T> |
ObservableCollection<T> |
|---|---|---|
| 通知 | なし | CollectionChanged / PropertyChanged |
| バインディング | 静的(追加が UI に反映されない) | 動的(自動反映) |
| パフォーマンス | 高速 | イベント発火分だけ遅い |
AddRange |
あり | なし(個別 Add) |
| 用途 | 一般的な順序付き集合 | バインディング対象 |
ObservableCollection<T> には AddRange がありません。多数の要素を一気に追加するとそのたびに通知が飛ぶため UI が遅くなります。バルク追加が必要なときは、
- 新しい
ObservableCollection<T>を作って差し替える Reset通知を 1 度だけ飛ばすカスタム派生クラスを作るRangeObservableCollection系のサードパーティ実装を使う
といった対処が一般的です。
派生で挙動を拡張する
Collection<T> を継承しているため、InsertItem / RemoveItem / SetItem / ClearItems をオーバーライドして 追加・削除・置換時の追加処理 を挟めます。
public class TrackedCollection<T> : ObservableCollection<T>
{
protected override void InsertItem(int index, T item)
{
// 検証など
base.InsertItem(index, item);
// ログ記録など
}
}
これは INotifyCollectionChanged のイベントを横から捕まえるよりクリーンな書き方です。
使いどころ
- WPF / MAUI / Avalonia / WinUI3 の MVVM:ViewModel が公開するリストの実装として事実上の標準。
- UI に表示するリアルタイムデータ:チャットの新着メッセージ、株価ティック、ログビューア など。
- 動的に変わる選択肢:ドロップダウンやリストボックスの項目を実行時に増減させるケース。
向かないケース
- UI と無関係なデータ集合 →
List<T>で十分。ObservableCollection<T>のオーバーヘッドは無駄。 - 大量データのバルク変更 → 個別通知でパフォーマンスが悪化する。バルク対応の派生型を使う。
- 並行アクセスが必須 → 標準では非スレッドセーフ。
EnableCollectionSynchronizationなどで補強する。
注意点
- イベントハンドラ内でコレクションを変更しない:再入で挙動が崩れる。
- 解除しないハンドラはメモリリークの元:購読側が長命オブジェクトなら明示的に
-=する。 AddRange相当はない:パフォーマンス重視ならカスタム派生か差し替えで対応。
まとめ
ObservableCollection<T>は 変更通知付きコレクション。MVVM とデータバインディングの主役。IList<T>/ICollection<T>/IEnumerable<T>に加え、INotifyCollectionChanged/INotifyPropertyChangedを実装。- 性能特性は
List<T>ベース。イベント発火と UI スレッド制約を理解して使う。 - 公開時は読み取り専用ビューが欲しいことも多い。次回は
ReadOnlyObservableCollection<T>を扱う。