はじめに
@okazuki さん がメンテナーを務めておられる ReactiveProperty に関する記事になります。
趣味のWPFアプリ開発で長い間 愛用させていただいているのですが、勘違い(勝手な思い込み)をしているところが見つかり、かずき さんに質問した内容をまとめた記事になります。
知ってる人にはとっては「今更何言っての?」って内容かもしれません 😭
本題
ReactiveCommand
を作成する場合、 WithSubscrbe()
を使って Action
の購読までメソッドチェーンで一気に書くことがあります。
以下のコードでは ICommand.CanExecute()
を 1秒ごとに切り替えます。
TimerCommand = Observable.Timer(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1))
.Select(x => (x & 1) == 0)
.ToReactiveCommand()
.WithSubscribe(() => Message = $"Clicked!", _disposables.Add);
WithSubscribe()
に Disposer
を指定しており、_disposables.Dispose()
を実行すれば 後腐れなく全てが破棄 される。 と思い込んでおりましたが、その認識は間違いでした。
実際に Disposer
に登録されるのは 購読した Action
のみで、 ReactiveCommand
自体は登録されません。
そのため上のコードでは、_disposables.Dispose()
後も Timer が動作し続けて、ReactiveCommand.CanExecute()
は変化し続けます。(Action
は破棄済みなので Command
を実行しても何も起きません)
対策
ReactiveCommand.WithSubscribe()
の使用時は ReactiveCommand
自体も破棄の対象に登録しましょう。
TimerCommand = Observable.Timer(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1))
.Select(x => (x & 1) == 0)
.ToReactiveCommand()
.WithSubscribe(() => Message = $"Clicked!", _disposables.Add)
.AddTo(_disposables); // ◆これが必要!!
思い込みって怖いですね。
サンプルコード
hsytkm/RpTimerDemo: Dispose ReactiveCommand when using WithSubscribe method.
環境
VisualStudio 2022 17.2.0 Preview 1.0
.NET 6.0 + C# 10.0 + WPF
ReactiveProperty 8.0.3