先日取り上げた、「ObservableCollection」でClearメソッド実行直前まで代入されていたアイテムたちを、CollectionChangedで取得するお話の続きです。

【C#備忘録】ObservableCollection.Clearメソッドの罠 (2018/2/27(火) 午後 7:21)
https://blogs.yahoo.co.jp/a32kita/15752321.html

こんなの実装してみました。

a32kita/ExtendedObservableCollection
https://github.com/a32kita/ExtendedObservableCollection

ちょっとその解説を…。

先行研究「ObservableCollectionの拡張」

まず、CollectionChangedイベントでClearメソッドされる直前の中身を取得する方法として、前回の記事の最後でObservableCollectionを継承した拡張クラスの実装についてご紹介致しました。
前回の記事は、結果としては未解決のまま終了してしまいました。

失敗の主な理由は、CollectionChangedのイベントデータ格納型であるNotifyCollectionChangedEventArgsクラスでは、ActionにコレクションのClearを表す「Reset」を設定したとき、変更されたアイテムの一覧を表すChangedItemsには何も設定が行えない、というところにありました。
NotifyCollectionChangedEventArgsのコンストラクタにResetを設定した状態で、ChangedItemsの設定を行おうとしてしまうと、ArgumentExceptionを吐かれてしまいます。
つまり、NotifyCollectionChangedEventArgs.OldItemsにReset前のコレクションの中身を放り込む行為は、.NETを創造した神様たちがお許しにならないということのようです。
神様の思し召しとなれば仕方がありません。それが天啓というのであれば、我々は抗うべきではありません。
OldItemsにデータを格納するのは諦めましょう。

というわけで、ObservableCollectionの拡張に加え、もう1つ拡張することにしました。

提案手法「NotifyCollectionChangedEventArgsの拡張」

まず前回の記事の末尾で述べた通り、ObservableCollectionを拡張した「ExtendedObservableCollection」なるものを定義しました。
ObservableCollectionを継承し、protectedなメソッド、ClearItemsをオーバーライドすることで、Clear時の処理を変更します。

しかし、これだけではCollectionChangedを通して、Clear実行前のコレクションの内容を通知することはできません。
だったらOldItemsではなく、NotifyCollectionChangedEventArgsに新しいプロパティを作ってしまえば良いというのが今回の提案手法です。

わざわざ既存のNotifyCollectionChangedEventArgsを拡張するという手段を選んだのは、CollectionChangedイベントとの互換性を保つためです。

イメージ 1
図1 定義したExtendedObservableCollectionChangedEventArgs

名前を付けるのが下手で、えらく長い名前になってしまいました。
名前をつけるのが得意な方、新しい名前を付けてやって下さい。

このクラスの特長は、OldItems、NewItemsに加え「PreviousItems」という名前のプロパティを有しているというところです。
ExtendedObservableCollectionでは、Clear時にこのPreviousItemsにそれまでの内容を格納します。

NotifyCollectionChangedEventArgsの子クラスであるため、CollectionChangedイベントでイベントデータとして飛ばすことが可能です。
もちろん、CollectionChangedをハンドルする際に必要に応じてNotifyCollectionChangedEventArgs型のイベントデータをExtendedObservableCollectionChangedEventArgs型へキャストしていただく必要がありますが。

その他拡張

List<T>では利用できるのにObservableCollection<T>では利用できなかったAddRangeをExtendedObservableCollection<T>では実装しました。
AddRangeを実行すると、アイテムが全て追加されたあとにCollectionChangedイベントが発生し、NewItemsで追加されたアイテムたちを取得できる形にしています。

評価実験

一応動作実験。
Clear時の挙動は完全にオーバーライドしてしまっている都合上、正しく動作するかわからないんですよね。

イメージ 2
図2 CollectionChangedをハンドルするメソッド

イメージ 3
図3 Clear直後のCollectionChanged発生時のイベントデータ

一応正しくデータは受け取れているようですが、WPFなんかに利用するために実装したPropertyChangedの挙動に不安が。。
WPFのMVVMの中でお使いになる際は、ClearとAddRangeの挙動にご注意下さい。
私のほうでも気がついたら適宜修正します。。

最後に

名前がクソなクラスを定義してしまいましたが、一応前回の記事の解決編ということにはなったのではないでしょうか。
一応今回この記事で取り上げているプログラムは完全ライセンスフリーと宣言しておきます。
使えると思った方、もしいらっしゃいましたらご自由にお使い下さい。