📧

【Rx】ReactivePropertyで配列を使ったら通知が来なくてハマった

2023/08/28に公開

概要

RxのReactivePropertyに配列を使った際に、配列の一部を書き換えた際に通知が来なくてハマったのでその備忘録です
記事内ではUnityのRxライブラリであるUniRxを使用しています

ReactivePropertyとは

ReactivePropertyは端的いうと値に変更があった際に通知してくれるRXの便利な機能です

サンプルコード

 ReactiveProperty<int> reactivePropertyInt = new ReactiveProperty<int>(1);

reactivePropertyInt.Subscribe(x => Debug.Log(x));

reactivePropertyInt.Value = 2;
//これは通知されない
reactivePropertyInt.Value = 2;
reactivePropertyInt.Value = 3;
//同じ値でもSetValueAndForceNotifyを使って通知させることができる
reactivePropertyInt.SetValueAndForceNotify(3);

出力

1
2
3
3

ReactivePropertyに配列を使った場合

さて、問題のReactivePropertyに配列を使った場合です

ReactiveProperty<int[]> reactivePropertyArray = new ReactiveProperty<int[]>(new int[] { 1, 2, 3 });

reactivePropertyArray.Subscribe(x =>
{
    var log = "";
    foreach (var d in x)
    {
	log += d + ",";

    }
    Debug.Log(log);
});

//配列そのものを書き換える
reactivePropertyArray.Value = new int[] { 4, 5, 6 };

//Valueで配列のデータを取得
var dat = reactivePropertyArray.Value;
//配列の一部を書き換えただけでは通知されない
dat[0] = 7;

//配列の一部を書き換えてからValueに値を入れて更新しても通知されない
dat[0] = 8;
reactivePropertyArray.Value = dat;

//Valueを直接書き換えても通知されない
reactivePropertyArray.Value[0] = 9;

//配列の一部を書き換えてからSetValueAndForceNotifyで更新
dat[0] = 10;
reactivePropertyArray.SetValueAndForceNotify(dat);

出力

1,2,3,
4,5,6,
10,5,6,

7,5,68,5,69,5,6が出力されてほしいのですが、この使い方だと出力されません
ReactivePropertyに配列を指定した場合、配列の一部を書き換えても通知が発行されないようです

ReactiveCollectionを使う

ReactiveCollectionはCollection要素に変更があった際に通知してくれる機能です

サンプルコード

ReactiveCollection<int> reactiveCollection = new ReactiveCollection<int>(new int[] { 1, 2, 3 });

//追加時に通知
reactiveCollection.ObserveAdd().Subscribe(x =>
{
    Debug.Log(x.Value + "が追加されました");
});

//削除時に通知
reactiveCollection.ObserveRemove().Subscribe(x =>
{
    Debug.Log(x.Value + "が削除されました");
});

//値変更時に通知
reactiveCollection.ObserveReplace().Subscribe(x =>
{
    Debug.Log(x.OldValue + "が" + x.NewValue + "に置き換えられました");
});

//Clear時に通知
reactiveCollection.ObserveReset().Subscribe(x =>
{
    Debug.Log("リセットされました");
});

//Collectionに追加
reactiveCollection.Add(4);
//Collectionから削除
reactiveCollection.Remove(4);
//Collectionの値を書き換え
reactiveCollection[0] = 7;
//CollectionをClear
reactiveCollection.Clear();

出力

4が追加されました
4が削除されました
1が7に置き換えられました
リセットされました

これだと値を書き換えた際に、書き換わった要素だけの通知が飛んでくるので
配列の一部を書き換えた際に、配列全体を取得するためにこのように記述しました

ReactiveCollection<int> reactiveCollection = new ReactiveCollection<int>(new int[] { 1, 2, 3 });

reactiveCollection.ObserveReplace().Subscribe(_ =>
{
    var log = "";
    foreach (var d in reactiveCollection.ToArray())
    {
	log += d + " ";

    }
    Debug.Log(log);
});

//配列の一部を書き換える
reactiveCollection[0] = 7;

出力

7 2 3

ObserveReplace()Subscribeしているので、初期値の1 2 3は通知されないことにご注意ください

まとめ

配列の一部を書き換えた際に、通知が飛んでくるコードの紹介をしました
もし他にいい方法がありましたら教えていただけると幸いです

Discussion