〰️

【C#, ObservableCollection, ReactiveProperty】コレクションに対する変更通知シーケンスの作成方法

2023/01/29に公開

少し間が空く度に毎回忘れる機能
自分用メモ 追記するかも

何かしら変更があったことだけ知りたい場合

コレクション<T>のIObservableを作成するサンプル
class A : BindableBase {
    private A nanika;
    public A Nanika {
        get { return fileName; }
        set { SetProperty(ref fileName, value); }
    }
}

var col = new ObservableCollection<A>();
var obs = Observable.Merge(
        col.CollectionChangedAsObservable().Select(x => true),
        col.ObserveElementPropertyChanged().Select(x => true)
    )
    .ToReadOnlyReactiveProperty();

ポイント

  • CollectionChangedAsObservable():追加、削除、クリアといったコレクション自体の変更通知を発行するシーケンス
  • ObserveElementPropertyChanged():コレクション要素<T>内のプロパティ変更通知を発行するシーケンス
  • .Merge():引数に渡されたシーケンスの内いずれかが発行されたタイミングで後続に流すシーケンス
  • 上2つをそれぞれ.Select(x => true)で適当に型を合わせて.Merge()して一つのシーケンスを作成する
  • あとは.ToReadOnlyReactiveProperty()等で保存し、適宜.Subscribe()して購読すれば良い

注意事項

  • サンプルの場合、状態がどう変化したかは分からないため限定的な用途となる
  • Aのプロパティは全てINotifyPropertyChangedを実装していることが必須 (例ではPrismのBindableBaseだが…)
  • Aのプロパティが代入等のSetterを通して更新された場合は通知が発行されるが、Aのプロパティ内部の状態が更新されても通知は発行されない
階層の深さによる挙動の違い
col[0].Nanika = new A();                   // 発行される
col[0].Nanika.Nanika = new A();            // 発行されない
col[0].Nanika.Nanika.Nanika = new A();     // 発行されない
// ︙
// 永遠に続く
  • 深く考えずにコレクションのインスタンス自体を代入等で置き換えてしまうと通知が一切発行されなくなるため、発行元となるコレクションはgetterのみのプロパティとした方が良い(と思う)
コレクションのインスタンスを置き換えた場合の挙動の違い
col[0].Nanika = new A();            // 発行される
col = otherCollection;              // なんか別のコレクションに置き換える
col[0].Nanika = new A();            // 発行されない
  • サンプルのソースで.ToReadOnlyReactiveProperty()をコメントアウトするとシーケンスに値が流れなくなる

    • IObservable<T>(=シーケンス)を.Subscribe()(=購読)しているオブザーバ(=観測者)の数が1以上の場合にシーケンスを実行して通知を発行する仕組みになっているものと思われる (未確認)
    • ReactiveProperty<T>ReadOnlyReactiveProperty<T>も引数でIObservable<T>を受け取った場合は内部で.Subscribe()しており、.ToReactiveProperty().ToReadOnlyReactiveProperty()が正にそれやってる
      つまりReactiveProperty<T>ReadOnlyReactiveProperty<T>それ自体がオブザーバでもあると言える

どうでもいいけど言葉ややこしすぎ

Discussion