【PRISM】ダイヤログで使ったReactivePropertyは忘れず破棄しよう【WPF】


# この記事は

PRISMライブラリの[Dialog Service](https://prismlibrary.com/docs/wpf/dialog-service.html)と、[ReactiveProperty](https://github.com/runceel/ReactiveProperty)を併用する場合には
**ちゃんとReactivePropertyを破棄しよう！**の話

:::note warn
Dialog Service内に限らず、使い終わったものは処分（破棄）するのがマナーです
:::

## どういうこと？

[ReactivePropertyの後始末 - かずきのBlog@hatena](https://blog.okazuki.jp/entry/2016/04/30/073755)

ReactiveProperty作者様のブログ（↑コレ）から一部引用させていただくと・・・

> Disposeしないといけないケース
> 他のIObservableをソースとしてReactivePropertyなんかを作ったときは、内部動作としてSubscribeをしているので、Disposeを呼ぶ必要があります。

### で、どういうこと？

さらに同記事から引用させていただくと・・・

> こういう時は、ReactivePropertyを持っているクラスが不要になったタイミングでDisposeを呼んでやらないと、予期せぬ動作をすることがあるかもしれません。

この**予期せぬ動作**に遭遇したので、経緯と対策（私はどうしたか）を書き残す

# なぜDialog Serviceなのか？

ダイヤログは、その性質上、呼び出しとクローズ処理が頻繁に行われる

つまり（それがシングルトンでない場合）、頻繁にインスタンスが生成され、もしそれが適切なタイミングで破棄されなければ、メモリに悪影響を及ぼすことが予想される

- ダイヤログはその頻度に応じて悪影響の度合いが大きくなりがちであり
- また今回も自分はダイヤログを使っていて気付いた

 ⇒ 以上の理由から、この記事ではダイヤログをやり玉にあげている

# 検証用のコード

デフォルトの`MainWindow`から自作`MyDialog`（ユーザーコントロール）を開くだけのアプリをつくる

`Prism.Wpf`のバージョンは`7.2.0`をつかった

## モデルとダイヤログの準備

### モデル

モデルは一定間隔で連番を生成する`ObservableProp`をもっている

超絶参考：[【Reactive Extensions】 Hot変換はどういう時に必要なのか？ - 問題の解決策「Hot変換」](https://qiita.com/toRisouP/items/c955e36610134c05c860#%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E6%B1%BA%E7%AD%96hot%E5%A4%89%E6%8F%9B)

```cs:ModelImpl.cs
// コンストラクター内
ObservableProp = Observable
    .Timer(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(2)) // はじめに3秒待って、2秒間隔
    .Do(x => Debug.WriteLine($"モデル{nameof(ObservableProp)}: {x}")) // ただの表示
    .Select(x => x)
    .Publish()
    .RefCount();

// 参考の記事を見ずに自力でやったときは
// Select文の中にDebug.Write書いたり、Publish以下HOT変換してなかったり・・・
```

### ダイヤログ

ダイヤログは、モデルの`Observable`をもとに`ToReactiveProperty`してつくられた
`_reactivePropertyAA`と`_reactivePropertyBB`をもっている

```cs:MyDialogViewModel.cs
// コンストラクター内

// Observableから流れてきた値と、ReactivePropertyの初期値を区別するために
// -77,-88を初期値として与えている
_reactivePropertyAA = model.ObservableProp.ToReactiveProperty(initialValue: -77);
_reactivePropertyBB = model.ObservableProp.ToReactiveProperty(initialValue: -88);

// _observerAA/BBは OnNext, OnCompletedをDebug.Writeするだけのオブザーバー
_reactivePropertyAA.Subscribe(_observerAA);
_reactivePropertyBB.Subscribe(_observerBB);
```

### Prism.Unity で DI 

モデルとダイヤログをそれぞれPrism.UnityのDIコンテナに登録する

```cs:App.xaml.cs
// Dependecy Injection用コンテナへ登録
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterSingleton<IMyModel, ModelImpl>(); // モデル
    containerRegistry.RegisterDialog<MyDialog, MyDialogViewModel>(); // ダイヤログ
}
```

# Case1: ReactivePropertyを破棄しなかった場合

メインウィンドウからダイヤログを開いて閉じる動作を3回行う
![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/44f108a8-041c-404d-c702-12847b76ed27.gif)

以下ログに説明を入れたもの。（キャプチャした時のログとは違うので、数字が異なる箇所もある）

```cs:Case1_ReactivePropertyを破棄しなかった場合
// 出力結果

Show Dialog ボタンが押されました!!
ModelImpl: コンストラクターが呼ばれました 17294952 // <- 末尾はGetHashCodeで得たインスタンス識別用
MyDialogViewModel: コンストラクターが呼ばれました 58713911

_reactivePropertyAA OnNext -77 // ReactiveProperty初期値
_reactivePropertyBB OnNext -88
MyDialog: ビューのコンストラクターが呼ばれました 50102218
OnDialogOpened

モデルObservableProp: 0
_reactivePropertyAA OnNext 0
_reactivePropertyBB OnNext 0
モデルObservableProp: 1
_reactivePropertyAA OnNext 1
_reactivePropertyBB OnNext 1
モデルObservableProp: 2
_reactivePropertyAA OnNext 2
_reactivePropertyBB OnNext 2

Close Dialog ボタンが押されました!! // <- ここでダイヤログを閉じた
OnDialogClosed
// ダイヤログがクローズした後でも、OnNextが実行され続ける
モデルObservableProp: 3
_reactivePropertyAA OnNext 3
_reactivePropertyBB OnNext 3
モデルObservableProp: 4
_reactivePropertyAA OnNext 4
_reactivePropertyBB OnNext 4

Show Dialog ボタンが押されました!! // <- もう1度ダイヤログを開く
MyDialogViewModel: コンストラクターが呼ばれました 22985394 // <- 初めのダイヤログとは違うインスタンスが生成される（PRISM DialogServiceの挙動）
_reactivePropertyAA OnNext -77
_reactivePropertyBB OnNext -88
MyDialog: ビューのコンストラクターが呼ばれました 35909463
OnDialogOpened

MyDialog: ビューのデストラクターが呼ばれました 50102218 // <- ここで初めに開いたダイヤログのデストラクタが呼ばれる
MyDialogViewModel: デストラクターが呼ばれました 58713911 // デストラクタが呼ばれるタイミングは事前にわからない

モデルObservableProp: 5
_reactivePropertyAA OnNext 5
_reactivePropertyBB OnNext 5
_reactivePropertyAA OnNext 5
_reactivePropertyBB OnNext 5
モデルObservableProp: 6
_reactivePropertyAA OnNext 6
_reactivePropertyBB OnNext 6
_reactivePropertyAA OnNext 6
_reactivePropertyBB OnNext 6
モデルObservableProp: 7
_reactivePropertyAA OnNext 7
_reactivePropertyBB OnNext 7
_reactivePropertyAA OnNext 7
_reactivePropertyBB OnNext 7

Close Dialog ボタンが押されました!! // 2回目のダイヤログを閉じる
OnDialogClosed

モデルObservableProp: 8
_reactivePropertyAA OnNext 8 // <- 1回目, 2回目のダイヤログのオブザーバーが動いている
_reactivePropertyBB OnNext 8
_reactivePropertyAA OnNext 8
_reactivePropertyBB OnNext 8
モデルObservableProp: 9
_reactivePropertyAA OnNext 9
_reactivePropertyBB OnNext 9
_reactivePropertyAA OnNext 9
_reactivePropertyBB OnNext 9

Show Dialog ボタンが押されました!! // <- 3回目のダイヤログを開く
MyDialogViewModel: コンストラクターが呼ばれました 61469371
_reactivePropertyAA OnNext -77
_reactivePropertyBB OnNext -88
MyDialog: ビューのコンストラクターが呼ばれました 66482253
OnDialogOpened
モデルObservableProp: 10
_reactivePropertyAA OnNext 10
_reactivePropertyBB OnNext 10
_reactivePropertyAA OnNext 10
_reactivePropertyBB OnNext 10
_reactivePropertyAA OnNext 10
_reactivePropertyBB OnNext 10
モデルObservableProp: 11
_reactivePropertyAA OnNext 11 // <- ダイヤログを開いた数 3 x ReactivePropertyの数 2 =>
_reactivePropertyBB OnNext 11 //    6 回の OnNext が実行されつづける
_reactivePropertyAA OnNext 11
_reactivePropertyBB OnNext 11
_reactivePropertyAA OnNext 11
_reactivePropertyBB OnNext 11
```

ダイヤログが破棄された（デストラクタが呼ばれた）あとでも
OnNextが実行されつづけており、不気味な感じがする。

この調子でダイヤログを開いて閉じてを繰り返すと
ダイヤログから見捨てられたオブザーバーが延々とOnNextを実行しつづけて・・・

:::note warn
ダイヤログビューモデルクラスのprivate変数であるReactivePropertyと
それをSubscribeしているオブザーバーが
ダイヤログがいなくなってから（デストラクタ後）も存続し続けている（ように見える）

この周辺の理解がまだ足りなく、もしかしたら重大な誤解をしているかもしれない（！？）
:::

# Case2: ReactivePropertyを破棄した場合

ダイヤログビューモデルを変更する

`ReactiveProperty`を`new`したタイミングで、同時に`_disposables`に`AddTo`しておいて
ダイヤログが閉じたときに、呼ばれる`OnDialogClosed`メソッドの中で`_disposables`を`Dispose()`する

```cs:MyDialogViewModel.cs
// コンストラクター内
// 末尾に .AddTo() を付加した
_reactivePropertyAA = model.ObservableProp.ToReactiveProperty(initialValue: -77).AddTo(_disposables);
_reactivePropertyBB = model.ObservableProp.ToReactiveProperty(initialValue: -88).AddTo(_disposables);

//Rxのdisposablesに破棄するものを詰め込んで、IDailogAwareのOnDialogClosedメソッドでDisposeする
private readonly CompositeDisposable _disposables = new CompositeDisposable();
public void OnDialogClosed() => _disposables?.Dispose();

```

実際に動作している動画

![Animation2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/a3ba0e2f-c275-d075-3162-7f4e021893d6.gif)

以下ログに説明を入れたもの。（キャプチャした時のログとは違うので、数字が異なる箇所もある）

```cs:Case2_ReactivePropertyを破棄した場合
// 出力結果

Show Dialog ボタンが押されました!!
ModelImpl: コンストラクターが呼ばれました 65158399
MyDialogViewModel: コンストラクターが呼ばれました 43339000

_reactivePropertyAA OnNext -77 // ReactiveProperty初期値
_reactivePropertyBB OnNext -88
MyDialog: ビューのコンストラクターが呼ばれました 14081900 // <- ここでダイヤログを開く
OnDialogOpened
モデルObservableProp: 0
_reactivePropertyAA OnNext 0
_reactivePropertyBB OnNext 0
モデルObservableProp: 1
_reactivePropertyAA OnNext 1
_reactivePropertyBB OnNext 1
モデルObservableProp: 2
_reactivePropertyAA OnNext 2
_reactivePropertyBB OnNext 2

Close Dialog ボタンが押されました!!
OnDialogClosed

_reactivePropertyAA Completed !! // ReactivePropertyのDisposeでOnCompleted()が呼ばれる
_reactivePropertyBB Completed !!

MyDialogViewModel: デストラクターが呼ばれました 43339000
MyDialog: ビューのデストラクターが呼ばれました 14081900
// 以後、コンソール出力なし
```

# おわりに

繰り返しになるが
ダイヤログのビューとビューモデルが破棄された（デストラクタが呼ばれた）あとでも
メンバ変数のオブザーバーが動きつづけるあたり、よくわからない

なににせよ、よそのクラスの`IObservable`を`ToReactiveProperty`したときには`AddTo()`することを
忘れないようにしておけばいいと思う

# ほか参考にさせていただいた記事など

本題よりも、RxのHot/Coldの理解に時間を割いた気がする

https://qiita.com/toRisouP/items/f6088963037bfda658d3

https://qiita.com/yutorisan/items/68bd68bbffe3c4f3ba32

- [Rx入門 (14) - Cold to Hot変換](https://blog.xin9le.net/entry/2012/01/20/223111)
- [Reactive Extensions再入門 その５「HotとCold」](https://blog.okazuki.jp/entry/20111107/1320677760)
- [Reactive Extensions再入門 その３６「ColdからHotへ！Publishメソッドと参照カウンタ？RefCountメソッド」](https://blog.okazuki.jp/entry/20120212/1329059831)
- [Reactive Extensions再入門 その４５「Scheduler」](https://blog.okazuki.jp/entry/20120304/1329923070)
- [ReactiveProperty を編む【step: 7 .NET Core WPF MVVM ReactiveProperty 入門 2020】](https://elf-mission.net/programming/wpf/getting-started-2020/step07/#ReactiveProperty_Dispose)
- [【ReactiveProperty】きちんとDisposeしよう | 泥庭](https://yone64.wordpress.com/tag/reactiveproperty/)