ある日の業務中のこと…
WPFアプリの開発中の出来事でした
他のクラスから受けとった値でReactiveCollection型のプロパティを更新
↓
Subscribeしているクラスで変更後の処理を実行
という流れを実装しようとしていましたのですが、
なんとサブスクライブした後の処理が実行されないのです🤔
以下のような実装になっていました(必要なusingの記載は省略しています)
// 値の管理を行うクラス
public class IntsService
{
// 整数値のコレクション(管理したい値)
public ReactiveCollection<int> Ints { get; set; } = [];
}
// 変更をサブスクライブする側のクラス
public class IntsSubscriber
{
private readonly IntsService intsService;
public IntsSubscriber()
{
this.intsService = new IntsService();
// 値の追加をサブスクライブ
// 本来はコンソール出力ではなく値変更後に実行したいメソッドを渡してました
this.intsService.Ints
.ObserveAddChanged()
.Subscribe(x => Console.WriteLine("値が追加されました"));
}
public void ChangeProperty()
{
// ReactiveCollectionの値を変更
this.intsService.Ints = new ReactiveCollection<int>()
.AddRange(new List<int> { 1, 2, 3 });
}
}
public static class ReactiveCollectionExtensions
{
// ReactiveCollectionではListのAddRangeが使えないため自前の拡張メソッドで実現
// 本題とは関係ないです
public static ReactiveCollection<T> AddRange<T>(this ReactiveCollection<T> collection, IEnumerable<T> items)
{
foreach (var item in items)
{
collection.Add(item);
}
return collection;
}
}
なぜ変更通知が飛ばなかったのか
皆さんはお分かりでしょうか?
原因はIntsSubscriber
のChangeProperty
メソッドにありました
public void ChangeProperty()
{
// ReactiveCollectionの値を変更
this.intsService.Ints = new ReactiveCollection<int>()
.AddRange(new List<int> { 1, 2, 3 });
}
ここで値を変更するためにReactiveCollectionをnewしているんですね
つまり、参照を書き換えてしまっているわけです
ReactiveCollectionは.NET標準のObservableCollectionを継承しており、
変更通知の仕様はCollectionChangedメソッドと同様になります
CollectionChangedの仕様を確認してみると以下の記載があります
項目が追加、削除、または移動されたとき、またはリスト全体が更新されたときに発生します。
同じ参照のコレクションに対する変更は通知されますが、
参照を書き換えてしまうとイベントが発火されないのです
じゃあなんで単純に値をAddする実装にしていなかったのかという話ですが、
直近で触っていたFlutterのプロパティ変更通知がそういう仕様になっていたからですね
ReactiveCollectionも同じ仕様だろうと勘違いをして、
こんなお粗末な実装をしていたわけです😩
解決方法
参照を変えずに値を変更すれば解決です
public void ChangeProperty()
{
// ReactiveCollectionの値を変更
this.intsService.Ints.AddRange(new List<int> { 1, 2, 3 });
}
参照差し替えには要注意です!