こういうやつです。
usingReactive.Bindings;usingReactive.Bindings.Extensions;usingSystem;usingSystem.ComponentModel;usingSystem.Reactive.Disposables;usingSystem.Reactive.Linq;namespaceWpfApp2{publicclassMainWindowViewModel:INotifyPropertyChanged,IDisposable{publiceventPropertyChangedEventHandlerPropertyChanged;privatereadonlyCompositeDisposable_disposables=newCompositeDisposable();publicReactiveProperty<string>FirstName{get;}publicReactiveProperty<string>LastName{get;}publicReadOnlyReactivePropertySlim<string>FullName{get;}publicMainWindowViewModel(){// 数が増えたりしてくると、ちょっと長くなっちゃう…FirstName=newReactiveProperty<string>("").AddTo(_disposables);LastName=newReactiveProperty<string>("").AddTo(_disposables);FullName=FirstName.CombineLatest(LastName,(f,l)=>$"{f}{l}").ToReadOnlyReactivePropertySlim().AddTo(_disposables);}publicvoidDispose()=>_disposables.Dispose();}}
この例では短いけど、増えてくるとね。
結論
これといった解決策はないですが、一応緩和する方法はあります。
緩和方法
モデルに書こうぜ
アプリのロジックが入ってるならモデルレイヤーに押しやれないか考えましょう。
メソッドに切り出そうぜ
ReactiveProperty
の初期化を private
メソッドにしようとするとプロパティを ReactiveProperty<string> Hoge { get; }
から ReactiveProperty<string> Hoge { get; private set; }
にしないといけなくて、もやっとする問題は、代入はコンストラクタでやって組み立てをメソッドにすることで緩和は出来ます。
usingReactive.Bindings;usingReactive.Bindings.Extensions;usingSystem;usingSystem.ComponentModel;usingSystem.Reactive.Disposables;usingSystem.Reactive.Linq;namespaceWpfApp2{publicclassMainWindowViewModel:INotifyPropertyChanged,IDisposable{publiceventPropertyChangedEventHandlerPropertyChanged;privatereadonlyCompositeDisposable_disposables=newCompositeDisposable();publicReactiveProperty<string>FirstName{get;}publicReactiveProperty<string>LastName{get;}publicReadOnlyReactivePropertySlim<string>FullName{get;}publicMainWindowViewModel(){FirstName=CreateFirstNameProperty();LastName=CreateLastNameProperty();FullName=CreateFullNameProperty();}// 本当はモデルとかと繋ぐ処理を書くprivateReactiveProperty<string>CreateFirstNameProperty()=>newReactiveProperty<string>("").AddTo(_disposables);// 今回はいらないけどprivateReactiveProperty<string>CreateLastNameProperty()=>newReactiveProperty<string>("").AddTo(_disposables);// 今回はいらないけどprivateReadOnlyReactivePropertySlim<string>CreateFullNameProperty()=>FirstName.CombineLatest(LastName,(first,last)=>$"{first}{last}").ToReadOnlyReactivePropertySlim().AddTo(_disposables);// 今回はいらないけどpublicvoidDispose()=>_disposables.Dispose();}}
取り立てて目新しいことではないですが、一応メモです。
思いついただけ
こうも書けますね。
usingReactive.Bindings;usingReactive.Bindings.Extensions;usingSystem;usingSystem.ComponentModel;usingSystem.Reactive.Disposables;usingSystem.Reactive.Linq;namespaceWpfApp2{publicclassMainWindowViewModel:INotifyPropertyChanged,IDisposable{publiceventPropertyChangedEventHandlerPropertyChanged;privatereadonlyCompositeDisposable_disposables=newCompositeDisposable();publicReactiveProperty<string>FirstName{get;}publicReactiveProperty<string>LastName{get;}publicReadOnlyReactivePropertySlim<string>FullName{get;}publicMainWindowViewModel()=>(FirstName,LastName,FullName)=InitializeProperties();private(ReactiveProperty<string>firstName,ReactiveProperty<string>lastName,ReadOnlyReactivePropertySlim<string>fullName)InitializeProperties(){varfirstName=newReactiveProperty<string>("").AddTo(_disposables);varlastName=newReactiveProperty<string>("").AddTo(_disposables);varfullName=firstName.CombineLatest(lastName,(f,l)=>$"{f}{l}").ToReadOnlyReactivePropertySlim().AddTo(_disposables);return(firstName,lastName,fullName);}publicvoidDispose()=>_disposables.Dispose();}}
何か今風っぽい感を出せるだけ。欠点としては、順番を間違えても気づきにくいところでしょうか。下のように。
usingReactive.Bindings;usingReactive.Bindings.Extensions;usingSystem;usingSystem.ComponentModel;usingSystem.Reactive.Disposables;usingSystem.Reactive.Linq;namespaceWpfApp2{publicclassMainWindowViewModel:INotifyPropertyChanged,IDisposable{publiceventPropertyChangedEventHandlerPropertyChanged;privatereadonlyCompositeDisposable_disposables=newCompositeDisposable();publicReactiveProperty<string>FirstName{get;}publicReactiveProperty<string>LastName{get;}publicReadOnlyReactivePropertySlim<string>FullName{get;}publicMainWindowViewModel()=>// FirstName「もしかして」LastName「私たち」FullName「入れ替わってる~!?」(LastName,FirstName,FullName)=InitializeProperties();private(ReactiveProperty<string>firstName,ReactiveProperty<string>lastName,ReadOnlyReactivePropertySlim<string>fullName)InitializeProperties(){varfirstName=newReactiveProperty<string>("").AddTo(_disposables);varlastName=newReactiveProperty<string>("").AddTo(_disposables);varfullName=firstName.CombineLatest(lastName,(f,l)=>$"{f}{l}").ToReadOnlyReactivePropertySlim().AddTo(_disposables);return(firstName,lastName,fullName);}publicvoidDispose()=>_disposables.Dispose();}}
数が増えるとコンパイラーにも検知されない、見ててもわかりにくい間違いを生みそうなので、今回は普通のメソッド切り出しのほうがいいように感じてます。
NG パターン
プロパティに初期化を直接書けばいいじゃないか
以下のようなノリですね。
publicReactiveProperty<string>FirstName{get;}=newReactiveProperty<string>("");
上記以上のことをしようとすると破綻します。ここでは他のフィールドやプロパティなどにはアクセスできないので、FullName を組み立てようとしたりすると詰みます。
じゃぁ => で書こう
毎回、違うインスタンスの ReactiveProperty が作られるのでやめたほうが良いケースが多いと思います。
例えば以下のようにやるとコンパイルは通りますが
// MainWindowViewModel.cspublicReactiveProperty<string>FirstName=>newReactiveProperty<string>().AddTo(_disposable);publicReactiveProperty<string>LastName=>newReactiveProperty<string>().AddTo(_disposable);publicReadOnlyReactivePropertySlim<string>FullName=>FirstName.CombineLatest(LastName,(f,l)=>$"{f}{l}").ToReadOnlyReactivePropertySlim().AddTo(_disposable);
FirstName や LastName や FullName にアクセスするたびに新しいインスタンスになるので思った通りには動きません。
varvm=newMainWindowViewModel();varf1=vm.FirstName;// 1 つ新しい ReactiveProperty のインスタンスが作られるvarl1=vm.LastName;// 1 つ新しい ReactiveProperty のインスタンスが作られるvarf2=vm.FirstName;// 1 つ新しい ReactiveProperty のインスタンスが作られるvarl2=vm.LastName;// 1 つ新しい ReactiveProperty のインスタンスが作られるvarfullName1=vm.FullName;// 新しい FirstName と LastName 用の ReactiveProperty が別途作られて、それを加工した結果を格納する ReadOnlyReactivePropertySlim が作られるvarfullName2=vm.FullName;// 新しい FirstName と LastName 用の ReactiveProperty が別途作られて、それを加工した結果を格納する ReadOnlyReactivePropertySlim が作られる// 値を設定してもf1.Value="Kazuki";l1.Value="Ota";// 別インスタンスなので当然同期されないConsole.WriteLine(f2.Value);// 空文字Console.WriteLine(l2.Value);// 空文字// FullName もそうねConsole.WriteLine(fullName1.Value);// 空白Console.WriteLine(fullName2.Value);// 空白// もちろん VM のもそうConsole.WriteLine(vm.FirstName.Value);// 空文字Console.WriteLine(vm.LastName.Value);// 空文字Console.WriteLine(vm.FullName.Value);// 空白
こう書けば、まだいけるケース
ちょっと初見殺し感あるかも。
usingReactive.Bindings;usingReactive.Bindings.Extensions;usingSystem;usingSystem.ComponentModel;usingSystem.Reactive.Disposables;usingSystem.Reactive.Linq;namespaceWpfApp2{publicclassMainWindowViewModel:INotifyPropertyChanged,IDisposable{publiceventPropertyChangedEventHandlerPropertyChanged;privatereadonlyCompositeDisposable_disposables=newCompositeDisposable();privateReactiveProperty<string>_firstName;publicReactiveProperty<string>FirstName=>_firstName??(_firstName=newReactiveProperty<string>("").AddTo(_disposables));privateReactiveProperty<string>_lastName;publicReactiveProperty<string>LastName=>_lastName??(_lastName=newReactiveProperty<string>("").AddTo(_disposables));privateReadOnlyReactivePropertySlim<string>_fullName;publicReadOnlyReactivePropertySlim<string>FullName=>_fullName??(_fullName=FirstName.CombineLatest(LastName,(f,l)=>$"{f}{l}").ToReadOnlyReactivePropertySlim().AddTo(_disposables));publicvoidDispose()=>_disposables.Dispose();}}
C# 8 からなら、これでいける
usingReactive.Bindings;usingReactive.Bindings.Extensions;usingSystem;usingSystem.ComponentModel;usingSystem.Reactive.Disposables;usingSystem.Reactive.Linq;namespaceWpfApp2{publicclassMainWindowViewModel:INotifyPropertyChanged,IDisposable{publiceventPropertyChangedEventHandlerPropertyChanged;privatereadonlyCompositeDisposable_disposables=newCompositeDisposable();privateReactiveProperty<string>_firstName;publicReactiveProperty<string>FirstName=>_firstName??=newReactiveProperty<string>("").AddTo(_disposables);privateReactiveProperty<string>_lastName;publicReactiveProperty<string>LastName=>_lastName??=newReactiveProperty<string>("").AddTo(_disposables);privateReadOnlyReactivePropertySlim<string>_fullName;publicReadOnlyReactivePropertySlim<string>FullName=>_fullName??=FirstName.CombineLatest(LastName,(f,l)=>$"{f}{l}").ToReadOnlyReactivePropertySlim().AddTo(_disposables);publicvoidDispose()=>_disposables.Dispose();}}
これは割といいかも。コードスニペット作ろうかな。
まとめ
最後の書きかた気に入ったかも。