IList<T>に互換性があって,要素の追加と削除をイベントで取れる…以外と知らない方が多いことで有名らしい ObservableCollection.
using System.Collections.ObjectModel 名前空間で提供されている便利なコレクション型です.

WPFをお使いの方であれば,誰でもご存知かと思われますが,ちょっとはまってしまった事例についてご紹介いたします.

ObservableCollection

ObservableCollection<T>は,IList<T>と互換性のあるリスト型コレクションの機能を提供してくれます.
List<T>との主な違いは,要素の追加や削除などのコレクションの内容の変化をイベントで取れるというところ.WPFでMVVMやろうとしたときに,リスト系コントロールのItemsSourceなんかで利用されます.

イメージ 1
図1 CollectionChanged イベント

イメージ 2
図2 CollectionChanged イベントのハンドラ

コレクションの中身が変更されると,CollectionChangedイベントが発火し,NotifyCollectionChangedEventArgs型のイベント引数の中にどう言った変更が為されたのか (例えば,要素の追加や削除など) の情報や,追加されたアイテム,削除されたアイテムの情報が格納されます.

今回取り上げるのは,このコレクション変化時に取得できる要素情報のお話です.

Clearメソッドとイベントデータ

ObservableCollection<T> には,IList<T> に定義されている Clear メソッドが実装されております.
Clear メソッドは,その名のとおり全ての要素をコレクションから削除するというもの.
この時私の中で問題になったのが,この時受け取れるイベント引数の中身です.

イメージ 3
図3 CollectionChanged イベントで利用できるデータ

図3には,Clearメソッド実行時に発生した CollectionChanged の中で利用できる変数の内容を示しております.
「sender」は,CollectionChanged の発火元である ObservableCollection のインスタンス,「e」にはこのイベントで受け取れるイベントデータが格納されております.

表1 ObservableCollection と Action の値
メソッド
Actionの値
Add
Add
Move
Move
Remove, RemoveAt
Remove
SetItem
Replace
Clear
Reset


CollectionChangedイベントで受け取れるイベント引数の中のActionは,Clear操作時,Resetという値になるようです.
ここで問題になるのが,Clear操作前にコレクションに格納されていた要素の取得….

アイテムの削除時にはOldItemsで削除されたアイテムを取得することが可能でした.
Resetでも同じことができるやろと高を括っていると,ClearのときはOldItemsがnullになっているため,NullReferenceExceptionを叩きつけられる羽目になります.

OldItemsで取得できないのならコレクション本体からということを企むも,そもそもCollectionChangedは,コレクションに変化が発生した後の話なので,もうコレクション本体には何も残っていません.


Clear時にそれまで入ってたアイテムを取る方法

現状のところ「新しいコレクション型を定義する」などの方法しかないかと思われます.
もう少し良いやり方が無いか現在模索中です。

というわけで,まず思いついたのが,ObservableCollectionの拡張.
ObservableCollectionを使ってたところをまるまる簡単に差し替えができるようにするためには,型の互換性を持たせておく必要があります.となれば,継承でなんとかできれば,それが一番手っ取り早いというものです.

さっそく継承して,Clearメソッド発生時のイベントデータにそれまでのコレクションの内容を放り込むようにしてみました.

イメージ 4
図4 Clearメソッド発生時の処理だけオーバーライドしたクラス

継承してもpublicな項目でoverrideできるものはありません.
しかし,protectedでClearItemsという形でoverride可能なそれっぽいメソッドが存在していました.

というわけでさっそく処理を書いてこれでいけると思い,実行してみた結果.

イメージ 5
図5 OnClearメソッド内で発生した例外

System.ArgumentException: 'Reset 操作は、項目を変更せずに初期化する必要があります。
パラメーター名:action'


Clearメソッド以外のコレクション操作は,通常のObservableCollectionと同様の動作をしました.
しかし,Clearメソッドを呼んだ途端に先ほど実装したClearItemsメソッドの中で例外が,,

NotifyCollectionChangedEventArgsのコンストラクタで例外を吐かれてしまいました.
どうやら,ActionにClearを設定するときは,Itemsには何も設定できないみたいですね.

拡張クラス側に新たに「Cleared」というイベントを実装して,そこでそれまで入ってた内容を取れるようにしたほうが良さそうですね….

時間があるときにサンプルプログラムをGistか何かで公開して,こちらにリンクを貼り付けたいと思います...
簡単なコードですので,わざわざサンプルを公開するほどでもないでしょうが….

では.


追記 2018/03/02
サンプル実装しました。
【C#小物】ExtendedObservableCollection (2018/3/2(金) 午前 1:00)
https://blogs.yahoo.co.jp/a32kita/15756307.html

テストコードとの兼ね合いでGistではなく通常のリポジトリになってしまいましたが、中身は単純です。