# 通知設計書 5-ObservableCollection<T>.CollectionChanged

## 概要

本ドキュメントは、.NET Runtimeにおける`ObservableCollection<T>.CollectionChanged`イベント通知の設計を記述する。このイベントは、`INotifyCollectionChanged`インターフェースの実装として、動的なデータコレクションの変更をリスナーに通知する。

### 本通知の処理概要

`ObservableCollection<T>.CollectionChanged`イベントは、`Collection<T>`を継承し`INotifyCollectionChanged`および`INotifyPropertyChanged`を実装したジェネリックコレクションクラスが提供するイベントである。項目の追加、削除、移動、置換、コレクションのクリア操作時に自動的にイベントが発火され、UIとのデータバインディングをサポートする。

**業務上の目的・背景**：WPF、UWP、Xamarin、MAUIなどのUIフレームワークでは、ItemsControlやListView等のコントロールにバインドされたコレクションの変更をリアルタイムにUIに反映させる必要がある。`ObservableCollection<T>`は、この要件を満たすための標準的な実装として提供されている。開発者は通常のコレクション操作（Add、Remove、Clear等）を行うだけで、自動的にUIが更新される。

**通知の送信タイミング**：各コレクション操作（Add、Insert、Remove、RemoveAt、Clear、Move、インデクサによる置換）の直後に発火する。CountプロパティとItem[]インデクサの`PropertyChanged`イベントも同時に発火される。

**通知の受信者**：`CollectionChanged`イベントを購読しているすべてのイベントハンドラが受信者となる。UIフレームワークのバインディングエンジンが主要なリスナーとなる。

**通知内容の概要**：`NotifyCollectionChangedEventArgs`を通じて、変更の種類（Action）、追加/削除された項目、変更位置（インデックス）が通知される。

**期待されるアクション**：受信者は変更の種類に応じてUIを更新する。Add/Removeでは該当項目のみ追加/削除し、Resetではリスト全体を再構築する。

## 通知種別

クラスイベント（アプリ内通知）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期 |
| 優先度 | 高（UIの即時更新に必要） |
| リトライ | なし（同期イベント） |

### 送信先決定ロジック

`CollectionChanged`イベントに登録されているすべてのイベントハンドラに対して、マルチキャストデリゲートとして通知が行われる。再入呼び出しが検出された場合、単一のリスナーでない限り`InvalidOperationException`がスローされる。

## 通知テンプレート

### イベント引数

| 項目 | 内容 |
|-----|------|
| イベント型 | `NotifyCollectionChangedEventHandler` |
| 引数型 | `NotifyCollectionChangedEventArgs` |
| sender | `ObservableCollection<T>`インスタンス（`this`） |

### ObservableCollection<T>クラス

```csharp
[Serializable]
public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
    public virtual event NotifyCollectionChangedEventHandler? CollectionChanged;
    protected virtual event PropertyChangedEventHandler? PropertyChanged;

    // コレクション操作メソッド
    public void Move(int oldIndex, int newIndex);

    // オーバーライドされた保護メソッド
    protected override void ClearItems();
    protected override void InsertItem(int index, T item);
    protected override void RemoveItem(int index);
    protected override void SetItem(int index, T item);
    protected virtual void MoveItem(int oldIndex, int newIndex);

    // イベント発火メソッド
    protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e);
    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e);

    // 再入防止
    protected IDisposable BlockReentrancy();
    protected void CheckReentrancy();
}
```

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| Action | 変更の種類 | 操作種別 | Yes |
| NewItems | 追加された項目のリスト | 操作対象の項目 | No |
| OldItems | 削除された項目のリスト | 操作対象の項目 | No |
| NewStartingIndex | 新しい項目の開始インデックス | 操作位置 | No |
| OldStartingIndex | 古い項目の開始インデックス | 操作位置 | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| プログラム実行 | Add(item) | 常に | 末尾に項目追加 |
| プログラム実行 | Insert(index, item) | 常に | 指定位置に項目挿入 |
| プログラム実行 | Remove(item) | 項目が存在する場合 | 項目を削除 |
| プログラム実行 | RemoveAt(index) | 常に | 指定位置の項目を削除 |
| プログラム実行 | this[index] = value | 常に | インデクサによる置換 |
| プログラム実行 | Move(oldIndex, newIndex) | 常に | 項目の移動 |
| プログラム実行 | Clear() | 常に | 全項目削除（Reset） |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| Remove失敗 | Remove(item)で項目が見つからない場合はfalseを返しイベントは発火しない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[コレクション操作メソッドの呼び出し] --> B[CheckReentrancy呼び出し]
    B --> C{再入呼び出しか?}
    C -->|Yes + 複数リスナー| D[InvalidOperationException]
    C -->|No or 単一リスナー| E[base.操作メソッド実行]
    E --> F[OnCountPropertyChanged]
    F --> G[OnIndexerPropertyChanged]
    G --> H[OnCollectionChanged]
    H --> I{CollectionChangedハンドラあり?}
    I -->|No| J[終了]
    I -->|Yes| K[_blockReentrancyCount++]
    K --> L[handler.Invoke]
    L --> M[_blockReentrancyCount--]
    M --> J
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（メモリ内イベント通知）

### 更新テーブル一覧

該当なし（メモリ内イベント通知）

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| InvalidOperationException | 再入呼び出しで複数のリスナーがある | コレクション変更をイベントハンドラ外で行う |
| ArgumentNullException | コンストラクタにnullのコレクションを渡した | nullチェックを行う |
| ArgumentOutOfRangeException | 不正なインデックスを指定 | インデックス範囲を確認 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | なし（制限なし） |
| 1日あたり上限 | なし（制限なし） |

### 配信時間帯

制限なし（いつでも発火可能）

## セキュリティ考慮事項

- イベントハンドラへの参照がコレクションのライフサイクルに影響を与える可能性
- シリアライズ時に`[NonSerialized]`属性でイベントハンドラは除外される
- マルチスレッド環境では適切な同期が必要（ObservableCollectionはスレッドセーフではない）

## 備考

- `System.Collections.ObjectModel`名前空間に定義
- `[Serializable]`属性が付与されているが、イベントハンドラはシリアライズされない
- 再入防止機能（`BlockReentrancy`/`CheckReentrancy`）が組み込まれている
- `EventArgsCache`クラスでPropertyChangedイベント引数がキャッシュされ、GC負荷を軽減
- 範囲操作（AddRange等）は標準ではサポートされていない（継承して実装可能）
- WPFのDispatcher経由でUIスレッドから使用する必要がある

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: クラス定義と継承関係を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ObservableCollection.cs | `src/libraries/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs` | クラス宣言とインターフェース実装（行18-23） |

**読解のコツ**: `Collection<T>`を継承し、`INotifyCollectionChanged`と`INotifyPropertyChanged`を実装している点を理解する。

**主要処理フロー**:
1. **行18-22**: クラス属性（Serializable, DebuggerTypeProxy等）
2. **行22**: クラス宣言と実装インターフェース
3. **行24-27**: フィールド定義（_monitor, _blockReentrancyCount）

#### Step 2: コンストラクタを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ObservableCollection.cs | `src/libraries/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs` | 3つのコンストラクタ（行32-63） |

**主要処理フロー**:
- **行32-34**: デフォルトコンストラクタ
- **行47-49**: IEnumerable<T>からの初期化
- **行61-63**: List<T>からの初期化

#### Step 3: 各操作メソッドを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ObservableCollection.cs | `src/libraries/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs` | ClearItems, RemoveItem, InsertItem, SetItem, MoveItem（行90-158） |

**主要処理フロー**:
- **行90-97**: `ClearItems` - Reset通知
- **行103-113**: `RemoveItem` - Remove通知
- **行119-127**: `InsertItem` - Add通知
- **行133-141**: `SetItem` - Replace通知
- **行147-158**: `MoveItem` - Move通知

#### Step 4: イベント発火メカニズムを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ObservableCollection.cs | `src/libraries/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs` | OnPropertyChanged, OnCollectionChanged（行163-199） |

**主要処理フロー**:
- **行163-166**: `OnPropertyChanged` - PropertyChangedイベント発火
- **行183-199**: `OnCollectionChanged` - 再入防止付きCollectionChanged発火

#### Step 5: 再入防止機能を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | ObservableCollection.cs | `src/libraries/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs` | BlockReentrancy, CheckReentrancy, SimpleMonitor（行214-312） |

**主要処理フロー**:
- **行214-218**: `BlockReentrancy` - 再入ブロックの開始
- **行223-235**: `CheckReentrancy` - 再入チェック
- **行296-312**: `SimpleMonitor` - IDisposable実装の内部クラス

### プログラム呼び出し階層図

```
外部からのコレクション操作（例：Add）
    │
    └─ Collection<T>.Add(item)
           │
           └─ InsertItem(Count, item)  // overridden
                  │
                  ├─ CheckReentrancy()
                  │      └─ if (_blockReentrancyCount > 0 && handler.HasMultipleTargets)
                  │             throw InvalidOperationException
                  │
                  ├─ base.InsertItem(index, item)
                  │
                  ├─ OnCountPropertyChanged()
                  │      └─ OnPropertyChanged(EventArgsCache.CountPropertyChanged)
                  │             └─ PropertyChanged?.Invoke(this, e)
                  │
                  ├─ OnIndexerPropertyChanged()
                  │      └─ OnPropertyChanged(EventArgsCache.IndexerPropertyChanged)
                  │             └─ PropertyChanged?.Invoke(this, e)
                  │
                  └─ OnCollectionChanged(Action.Add, item, index)
                         │
                         ├─ _blockReentrancyCount++
                         │
                         ├─ CollectionChanged?.Invoke(this, e)
                         │
                         └─ _blockReentrancyCount--
```

### データフロー図

```
[入力]                     [処理]                          [出力]

Add/Remove/etc. ───▶ CheckReentrancy() ───▶ 再入チェック
        │                    │                       │
        │                    ▼                       ▼
        │            base.InsertItem()等      InvalidOperationException
        │                    │                (複数リスナー+再入時)
        │                    ▼
        │        OnCountPropertyChanged()
        │        OnIndexerPropertyChanged()
        │                    │
        ▼                    ▼
コレクション状態変更   OnCollectionChanged()
        │                    │
        │                    ▼
        │            _blockReentrancyCount++
        │            CollectionChanged?.Invoke()
        │            _blockReentrancyCount--
        │                    │
        ▼                    ▼
新しいコレクション状態   UIの更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ObservableCollection.cs | `src/libraries/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs` | ソース | メインクラス実装 |
| INotifyCollectionChanged.cs | `src/libraries/System.ObjectModel/src/System/Collections/Specialized/INotifyCollectionChanged.cs` | ソース | 実装インターフェース |
| INotifyPropertyChanged.cs | `src/libraries/System.ObjectModel/src/System/ComponentModel/INotifyPropertyChanged.cs` | ソース | 実装インターフェース |
| NotifyCollectionChangedEventArgs.cs | `src/libraries/System.ObjectModel/src/System/Collections/Specialized/NotifyCollectionChangedEventArgs.cs` | ソース | イベント引数 |
| PropertyChangedEventArgs.cs | `src/libraries/System.ObjectModel/src/System/ComponentModel/PropertyChangedEventArgs.cs` | ソース | イベント引数 |
| ReadOnlyObservableCollection.cs | `src/libraries/System.ObjectModel/src/System/Collections/ObjectModel/ReadOnlyObservableCollection.cs` | ソース | 読み取り専用ラッパー |
