runceel / ReactiveProperty

ReactiveProperty provides MVVM and asynchronous support features under Reactive Extensions. Target frameworks are .NET 6+, .NET Framework 4.7.2 and .NET Standard 2.0.
MIT License
903 stars 101 forks source link

型の異なるオブジェクトに対してネストしたプロパティをObservePropertyする #248

Closed kenichi-kobayashi closed 3 years ago

kenichi-kobayashi commented 3 years ago

いつも、ReactivePropertの保守、開発ありがとうございます。 前回のIFilteredのReset対応の実験がまだ出来ていなくて連絡が遅くなってしまって、申し訳ないです。

そして、平行してV7.5.0で追加された「ネストしたプロパティのObserveProperty 」を使っています。 これを使うことで、劇的にスリム化されたコードがかける可能性があることを感じています。

そこで、一つ仕様を確認させてください。

ネスト対象のプロパティが、既定クラスが統一された別クラスが指定された場合、正しく動作しないのですが、これは、仕様でしょうか? 」 サンプルプログラムを作ってみました。 GrandBaseから派生したGrandAとGrandBを定義しています。

public abstract class GrandBase
{
    public string Name { get; set; }
    public Parent Parent { get; set; } = new Parent();
}

public class GrandA : GrandBase
{
}

public class GrandB : GrandBase
{
}

ReactivePropertyTest.zip

そして、上記のGrandBaseを格納できるRpを用意します。

public ReactivePropertySlim<GrandBase> GrandRp { get; set; } = new ReactivePropertySlim<GrandBase>();

このGrandRpからネスト関したしたRpを作ります。

var ChildName = GrandRp.ObserveProperty(a => a.Value.Parent.Child.Name).ToReadOnlyReactivePropertySlim().AddTo(disposables);

その場合に、ChildNameが空になって取得できませんでした。 これは、仕様でしょうか? これができると、使い方がかなり広がるので、非常にありがたいです。

ご検討のほど、よろしくお願いします。

runceel commented 3 years ago

まだ深く追えてませんが、おそらく想定していない条件なのでバグだと思います。 確認してみます。

runceel commented 3 years ago

プロパティの Get アクセサーをリフレクションで取得してキャッシュしているのですが、型が途中で変わると Get アクセサーの呼び出しで型のミスマッチが起きてこけていました。

型が変わったタイミングで、Get アクセサーのキャッシュをクリアするようにして対応しました。

kenichi-kobayashi commented 3 years ago

ありがとうございます。 相変わらずの爆速対応、感謝です。早速取り込んでみます。

kenichi-kobayashi commented 3 years ago

NugetからV7.8.2を取得して、実験してみましたが、正しく動作しませんね。

ReactivePropertyTest.zip

上記プロジェクトで画面中央のボタンを押下すると、画面のテキストは「Grand2-Name3」になってほしいのですが、空になってしまいますね。

runceel commented 3 years ago

そちらは、また別の問題で ObserveProperty で監視しているプロパティ上の全ての型が INotifyPropertyChanged を実装していないために起きているのだと思います。

v7.8.3 で ObserveProperty で監視するプロパティの途中に INotifyPropertyChanged を実装していない型が来ているケースにも対応してみました。

runceel commented 3 years ago

整理すると以下の 2 つの問題がありました。

  1. GrandBase と、その派生クラスで INotifyPropertyChanged を実装していないので動いていなかった
  2. GrandBase が INotifyPropertyChanged を実装していても、途中で型がさし変わると例外が出る問題があった

最初の v7.8.2 では 2 を解消して v7.8.3 では 1 を解消しました。 なので、v7.8.2 だと GrandBase に INotifyPropertyChanged を実装すると期待通りに動いて v7.8.3 だと修正しなくても期待通り動くと思います。

私も最初 1 のほうの動きを見落としていました。すいません。

kenichi-kobayashi commented 3 years ago

かずきさん、修正を確認しました。無事に動きました。 本当に、ありがとうございました! 今回のネストしたプロパティの監視機能は、かなり強力で、すっきり記載ができるようになりました。 これ、かなり強力です。

そして、一つ質問させてください。

public ReactivePropertySlim SelectedNode { get; }

DisplayFullPath = SelectedNode.ObserveProperty(a => a.Value.DisplayFullPath).ToReadOnlyReactivePropertySlim().AddTo(disposables);

上記のようなコードを定義したとします。

SelectedNode.Value = null;

この状態で、上記のコードを書いた場合、ObserveProperty内の「a.Value.DisplayFullPath」で例外が発生するよな気がしていたのですが、特に例外にならなかったんですよね。

ネストしたプロパティでヌルポなりで、例外が発生した場合は、Defaultに戻すような仕様になっていますか? 動き的に、そのような動きに見えました。

runceel commented 3 years ago

ObserveProperty の引数に渡している a.Value.DisplayFullPath は式木なので、それを分析して監視対象のプロパティ名を取得するのに使っています。 途中の値が変わったときは、プロパティを 1 つずつ辿って行って途中で null があったら default を返すような感じにしてます。

コード的にはわかりにくいかもしれませんが以下のフォルダーにある PropertyObservable.csPropertyPathNode.cs あたりが実際の ObserveProperty でネストしたプロパティを渡したときの実態のコードです。

https://github.com/runceel/ReactiveProperty/tree/main/Source/ReactiveProperty.NETStandard/Internals

runceel commented 3 years ago

一旦 Issue 自体はクローズにします。