【WPF】BooleanNotifierをToReactiveCommandしたときの挙動について【ReactiveProperty7.5.1】

# 2020年12月20日深夜

[ReactiveProperty](https://github.com/runceel/ReactiveProperty)の`BooleanNotifier`クラスに関する記事です。

`ToReactiveCommand`したときに、事故りそうなケースをまとめました。
長くなったので、最後のまとめを読んでいただければと思います。

# 2020年12月21日真夜中　追記

「なぜそうなっているのか」について、2011年の記事を引用して背景を詳しく説明されています。
[BooleanNotifierをToReactiveCommandしたときの挙動、が思っていたのと違う理由](https://qiita.com/soi/items/78b4c0d1ebc68bd4e79d)
（@soi様から頂いたコメントより）

# 2020年12月24日昼間　追記
（@okazuki様から頂いたコメントを受け追記）

[公式のドキュメント](https://okazuki.jp/ReactiveProperty/features/Notifiers.html#booleannotifier)が増補されました。
日頃`ReactiveProperty`を使わせていただいている身としては恐れ多い限りです。

先述のsoi様の記事にある通り、使いどころを考えて使うのが大前提でありつつ
もしつかうのであれば、引数に`initialValue: n.Value`として変数のステータスを渡すのがシンプルかつ安全です。

![1218_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/c5264beb-61af-dc0a-ef72-6f93e99e804e.png)
キャプチャ画像出典：https://okazuki.jp/ReactiveProperty/features/Notifiers.html#booleannotifier



## 概要

私はC#のWPFアプリケーションを作っています。
使用しているライブラリの1つは[ReactiveProperty](https://github.com/runceel/ReactiveProperty)です。
`ReactiveProperty`はNuGetパッケージマネージャーからインストール可能です。
2020年12月18日現在の最新版は**7.5.1**です。

![1218_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/3c389c7e-adc5-73d6-381e-718f3763397b.png)


先日、`ReactiveProperty`の`BooleanNotifier`クラスを使っていて、少し混乱することがありました。
よくよく調べてみると、混乱が解消されました。
この記事は、混乱したことと解消の経緯について書き残す目的があります。

## 検証環境

- Windows 10 Home
- Visual Studio 2019 Community
- **ReactiveProperty 7.5.1**

## BooleanNotifierクラスの簡単な説明

`BooleanNotifier`は`IObservable<bool>`を実装したシンプルなクラスです。
掃除機の電源ボタンのように、オン（`True`）とオフ（`False`）をスイッチする使い方ができます。

特徴|嬉しいこと（個人の感想）
---|---
`IObservable<bool>`を実装している| `ToReactiveCommand`の拡張メソッドが使える
`SwitchValue`メソッドを備えている| **`True`と`False`の切り替え（トグル）をスタイリッシュに記述できる**

真偽値のトグルを、`ReactiveProperty`と`BooleranNotifier`のそれぞれで書いてみます。

```cs
// （１）ReactiveProptertyで書いた場合
var m = new ReactiveProperty<bool>();
m.Value = !m.Value;

// （２）BooleranNotifierで書いた場合
var n = new BooleanNotifier();
n.SwitchValue();
```

`BooleanNotifier`の詳細な説明は、下記ドキュメントに記載されています。

[ReactiveProperty documentation - BooleanNotifier](https://okazuki.jp/ReactiveProperty/features/Notifiers.html#booleannotifier)

## 【本題】BooleanNotifierを使って混乱したこと

`BooleanNotifier`は`IObservable`を実装しているので、`ReactiveCommand`のソースにできます。
> It can use to source of ReactiveCommand.

（引用元：[ReactiveProperty documentation - BooleanNotifier](https://okazuki.jp/ReactiveProperty/features/Notifiers.html#booleannotifier)）

さっそく、`ToReactiveCommand`拡張メソッドで`ReactiveCommand`をつくってみます。
比較参考のために、`ReactiveProperty<bool>`も使います。

### 成果物のゴールイメージ

- `ReactiveCommand`をバインドしたボタンを配置します。
- 画面起動時のボタンは非活性（押せない状態）にします。

### ビューモデル

ビューモデルは以下のようになります。
Prismテンプレートを使っています。

```cs
public class PrismWindow2ViewModel : BindableBase
{
    // これらのコマンドをボタンにバインドします
    public ReactiveCommand BooleanNotifierButtonCommand { get; }
    public ReactiveCommand ReactivePropertyButtonCommand { get; }

    public PrismWindow2ViewModel()
    {
        var booleanNotifier= new BooleanNotifier(initialValue: false);
        var reactiveProperty = new ReactiveProperty<bool>(initialValue: false);

        BooleanNotifierButtonCommand = booleanNotifier.ToReactiveCommand();
        ReactivePropertyButtonCommand = reactiveProperty.ToReactiveCommand();
    }
}
```

### ビュー

ビューは以下のようになります。
`BooleanNotifier`をソースにしたボタンを上方に、`ReactiveProperty`をソースにしたボタンを下方に配置してます。

```cs
<Window x:Class="ReactivePropertyPlayground.Views.PrismWindow2"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True"
             Height="150" Width="300">
    <StackPanel>
        <Label Content="ソースの初期値は false"/>
        <Button 
            Command="{Binding BooleanNotifierButtonCommand}" 
            Content="booleanNotifier.ToReactiveCommand();"
            Margin="10"/>
        <Button 
            Command="{Binding ReactivePropertyButtonCommand}" 
            Content="reactiveProperty.ToReactiveCommand();"
            Margin="10"/>
    </StackPanel>
</Window>
```

### 実行画面

Visual Studioで**F5キーを押して実行した直後の画面**が以下のようになります。

![1218_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/26b196a9-17f5-1f35-a99c-815f0fa63318.png)


`ReactiveCommand`のソース|画面起動直後の状態
---|---
`BooleanNotifier(initialValuse: false)`|**押せる**
`ReactiveProperty(initialValuse: false)`|**押せない**

### 期待していたイメージと違う

ビューモデルでは、ソースの初期値を`false`にしていました。

```cs
var booleanNotifier= new BooleanNotifier(initialValue: false);
var reactiveProperty = new ReactiveProperty<bool>(initialValue: false);
```

**ボタンのソースの初期値はどちらも`initialValuse: false`なのに、なぜ画面起動直後のボタンの状態に差があるのか？**

画面起動時はどちらのボタンも非活性な状態であることを期待していました。
しかし、実際は`BooleanNotifier`をソースにしたボタンは押せる状態でした。
`ReactiveProperty`は押せない状態であり、期待通りでした。

#### 期待通りにするためには

画面起動時に、ボタン（`ReactiveCommand`バインド）を非活性にするためには、`ToReactiveCommand`拡張メソッドの引数に初期値`false`を与えます。

```cs
BooleanNotifierButtonCommand = booleanNotifier.ToReactiveCommand(initialValue:false);
```

以上です。
これで期待通りに動きます。



## 【裏コース】デフォルト引数の話

### 疑問

`ToReactiveCommand`に引数をあたえることで、確実にバインドしたボタンの初期状態を既定できることが分かりました。
しかし、若干の二度手間感が残りました。

- `IObservable`のソースに`initialValuse`を渡して、なおかつ`ToReactiveCommand`の引数にも同じように`initialValue`を渡す必要があるのはなぜか？
- **ボタンのソースの初期値はどちらも`initialValuse: false`なのに、なぜ画面起動直後のボタンの状態に差があるのか？**（再掲）
- `BooleanNotifier`と`ReactiveProperty`の違いはなにか？

ソースの`initialValuse`を踏襲してボタンの活性/非活性が決まるものと勝手に思い込んでいましたが、そうではないみたいです。

### ソースコードを読んでみる

`ReactiveProperty`はGitHubスター数500を超える超優良ライブラリです。
そんなソースコードが誰でも閲覧できます。

`BooleanNotifier`がどういう経路をたどって`ReactiveCommand`になるのかもソースコードを読めば分かります。

#### `ToReactiveCommand`拡張メソッド

第二引数のデフォルト引数は`true`です。
第二引数を指定しなければ、ボタンは活性化した状態になることが推定されます。

```cs
public static ReactiveCommand ToReactiveCommand(this IObservable<bool> canExecuteSource, bool initialValue = true) =>
            new ReactiveCommand(canExecuteSource, initialValue);
```

（引用元：https://github.com/runceel/ReactiveProperty/blob/main/Source/ReactiveProperty.NETStandard/ReactiveCommand.cs）


しかし、`ReactiveProperty`をソースにしたボタンは、`ToReactiveCommand`の第二引数を指定しなくとも、初期状態が非活性のボタンが生成されました。

#### `ReactiveCommand`

コマンドの活性/非活性は`ICommand`のインターフェイスの`CanExecute()`の実装で決まります。

`ReactiveCommand`の活性/非活性は`private`プロパティの`IsCanExecute`で決まるようです。

```cs
public bool CanExecute() => IsCanExecute;
```

（引用元：https://github.com/runceel/ReactiveProperty/blob/main/Source/ReactiveProperty.NETStandard/ReactiveCommand.cs）

##### `ReactiveCommand`コンストラクター

```cs
public ReactiveCommand(IObservable<bool> canExecuteSource, IScheduler scheduler, bool initialValue = true)
{
    IsCanExecute = initialValue;
    Scheduler = scheduler;
    CanExecuteSubscription = canExecuteSource
        .DistinctUntilChanged()
        .ObserveOn(scheduler)
        .Subscribe(b =>
        {
            IsCanExecute = b;
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        });
}
```

（引用元：https://github.com/runceel/ReactiveProperty/blob/main/Source/ReactiveProperty.NETStandard/ReactiveCommand.cs）

`initialValuse`は`ToReactiveCommand`と同じ値をとるので、`IsCanExecute`は`true`になります。

ソースの`canExecuteSource`にはメソッドチェーンで処理がぶら下がっており、最後にお馴染みの`Subscribe`で`IsCanExecute`プロパティにソースの値を代入しています。

ソースである`BooleanNotifier`と`ReactiveProperty`の値が変更されたら、ボタンの状態も変わりそうです。

**`BooleanNotifier`も`ReactiveProperty`も`new`しかしていない。**
**ソース側の`Subscribe`で何かしている？**

参考）メソッドチェーンの途中の処理については本家okazuki殿の記事を参照ください。

[Reactive Extensions再入門 その２３「重複を排除するメソッド」](https://blog.okazuki.jp/entry/20120104/1325679391)
[Reactive Extensions再入門 その４５「Scheduler」](https://blog.okazuki.jp/entry/20120304/1329923070)

#### `BooleanNotifier`と`ReactiveProperty`

`BooleanNotifier`を購読すると、値が`set`されたときに`OnNext`で通知がなされます。

```cs
public IDisposable Subscribe(IObserver<bool> observer) => boolTrigger.Subscribe(observer);

// ...

public bool Value
{
    // 中略
    set
    {
        boolTrigger.OnNext(value);
    }
}
```

（引用元：https://github.com/runceel/ReactiveProperty/blob/main/Source/ReactiveProperty.NETStandard/Notifiers/BooleanNotifier.cs）

一方、`ReactiveProperty`では

```cs
public IDisposable Subscribe(IObserver<T> observer)
{
    // 中略
    if (IsRaiseLatestValueOnSubscribe)
    {
        observer.OnNext(LatestValue);
    }
    // 中略
}
```

（引用元：https://github.com/runceel/ReactiveProperty/blob/main/Source/ReactiveProperty.NETStandard/ReactiveProperty.cs）

念のため`if`条件が`True`になることも確認しました。

```cs

System.Console.WriteLine(reactiveProperty.IsRaiseLatestValueOnSubscribe);
// コンソール出力: True
```


**`Subscribe`の中で`OnNext()`が記述されているため、`ToReactiveCommand`して購読した時点で現在の真偽値がバインド先に通達されていた**
ということが分かりました。

## まとめ

だんだん自分でも何が言いたいのか分からなくなってきました。
まとめます。

`BooleanNotifier`の場合は、ソースの状態をボタンへ反映させるために、`ToReactiveCommand`の引数に初期状態を渡す必要があります。

```cs
BooleanNotifierButtonCommand = booleanNotifier.ToReactiveCommand(initialValue:false);
```

`ReactiveProperty`の場合は、ソースの状態は勝手にボタンに反映されます。

裏を返せば、`ToReactiveCommand`の引数に渡す真偽値は意味を持ちません。（ソースの状態が優先されます）

### 事故ってるパターン

- `BooleanNotifier`は非活性にしたいのに、非活性になってない
- `ReactiveProperty`は`ToReactiveCommand`に引数`true`を与えて活性にしたいと思ったのに、ソースが優先されて非活性になっている。ボタンが押せない。

```cs
public PrismWindow2ViewModel()
{
    var booleanNotifier= new BooleanNotifier(initialValue: false);
    var reactiveProperty = new ReactiveProperty<bool>(initialValue: false);

    BooleanNotifierButtonCommand = booleanNotifier.ToReactiveCommand();
    ReactivePropertyButtonCommand = reactiveProperty.ToReactiveCommand(initialValue: true);
}
```

![1218_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/26b196a9-17f5-1f35-a99c-815f0fa63318.png)

## 以上です

何か間違いがございましたら、指摘していていただければ幸いです。