[Rx] 変更通知の合成はObservable.Merge<object>です。

WPFでBindingする時にLivetを使っていたのですが、ReactivePropertyが面白そうだったのではじめました。
Livetだと自分の好きなタイミングで変更通知イベントを出せていたのですが、Rxだとそのタイミングもロジックとして書く必要があります。

要は、AプロパティとBプロパティのどちらかが変わった時に、Cプロパティの変更通知を出したい みたいな

Livet

Livet で書くとこんな感じ

public class LivetViewModel : ViewModel {
  public int A {
    get => _A;
    set {
      if( _A != value ){
        _A = value;
        this.RaisePropertyChanged();
        this.RaisePropertyChanged( nameof(this.C) );
      }
    }
  }
  int _A;
  
  public int B {
    get => _B;
    set {
      if( _B != value ){
        _B = value;
        this.RaisePropertyChanged();
        this.RaisePropertyChanged( nameof(this.C) );
      }
    }
  }
  int _B;
  
  // A, B が変わった時にCの値が変わる
  public int C => A + B;
}

ReactiveProperty

だいたいCombineLatestを使えと書いてあるのでこんな感じ

public class RxViewModel {
  public RxViewModel() {
    this.C = this.A.CombineLatest(this.B, (a,b)=> a+b).ToReactiveProperty();
  }

  public ReactiveProperty<int> A { get; } = new ReactiveProperty<int>();
  public ReactiveProperty<int> B { get; } = new ReactiveProperty<int>();
  public ReactiveProperty<int> C { get; }
}

ReadOnlyとかSlimとかしっかり使うとこんな感じ

public class RxViewModel2 {
  public RxViewModel2() {
    this.C = this.A.CombineLatest(this.B, (a,b)=> a+b).ToReadOnlyReactivePropertySlim();
  }

  public ReactivePropertySlim<int> A { get; } = new ReactivePropertySlim<int>();
  public ReactivePropertySlim<int> B { get; } = new ReactivePropertySlim<int>();
  public ReadOnlyReactivePropertySlim<int> C { get; }
}

ただし、CombineLatest だと全ての値がそろわないと通知が始まらないので「値がない場合~」とかはできません。

やっと本題 Observable.Merge<object> を使いましょう。

public class RxMerge {
  public RxMerge() {
    this.C = Observable.Merge( this.A, this.B )
              .Select(x => this.A.Value + this.B.Value ).ToReadOnlyReactivePropertySlim();
  }

  public ReactivePropertySlim<int> A { get; } = new ReactivePropertySlim<int>();
  public ReactivePropertySlim<int> B { get; } = new ReactivePropertySlim<int>();
  public ReadOnlyReactivePropertySlim<int> C { get; }
}

CombineLatest の謎のラムダ式 (a,b)=> a+b も消えてスッキリしました。

全ての型が同じ場合は型推論ジェネリックの<T>部分を書かなくていいですが、複数の型が混ざる場合は<object>とすれば問題ありません。


そしてそして、本当にやりたかったことは ReactiveCommand のコマンドが実行可能かどうかという CanExecute 部分の書き方なのだった・・・

Observable.Merge<object>(
    // 変更通知 が来たら条件判定したいReactiveProperty
    this.A, this.B
  ).Select( x => {
    // True になる条件
    return this.A.Value != 0 && this.B.Value != 0;
  } )
	.ToReactiveCommand()

複数の条件が複雑に混ざり合うような時にとても重宝します。


追記 2018/10/18
ジェネリックのキャストの都合上IObservable<[struct]>がIObservable<object>にキャストできないので無理やり変換します。

こんなの用意して

public static Observable {
  public static IObservable<object> Cast<T>( this IObservable<T> source ) where T: struct {
    return source.Select( x => (object)x );
  }
}

こうだ!

public class RxMerge {
  public RxMerge() {
    this.C = Observable.Merge( this.A.Cast(), this.B )
                .Select(x => this.A.Value + int.Parse( this.B.Value ) ).ToReadOnlyReactivePropertySlim();
  }

  public ReactivePropertySlim<int> NumItem { get; } = new ReactivePropertySlim<int>();
  public ReactivePropertySlim<string> StrItem { get; } = new ReactivePropertySlim<string>();
  public ReadOnlyReactivePropertySlim<int> C { get; }
}


2019/01/29 追記
ToUnit() で IObservable に変換するのがデフォらしい。
いまさら聞けないReactive Extensions.3 - Qiita

Unit は戻り値なしのvoid的なやつだそうです。
でもデバッグ的にはIObservable<object> のほうが便利だなー

// ReactiveProperty.dll
namespace Reactive.Bindings.Extensions {
	public static class ToUnitObservableExtensions {
		public static IObservable<Unit> ToUnit<T>( this IObservable<T> self );
	}
}

// こんな感じ
Observable.Merge( this.A.ToUnit(), this.B.ToUnit() )