DI コンテナを利用しているプロジェクトで、リソースを破棄する処理をしようと思ったとき、CompositeDisposable を利用するという選択肢があります。
- Window を閉じて終了するとき、Window の情報の〇〇を保存する
- アプリを終了するとき、ゲームの情報の△△を保存する
こういう感じの終了処理はよくあるんですが、どこにコードを書いておくのかで悩むところ。コードの場所が散らばって、タイミングがズレてしまったりしても好ましくない。
CompositeDisposable を利用すると、あらかじめ登録しておいた Dispose メソッドの処理をまとめて実行することができます。なので、終了処理の起点をまとめてしまうことができるので便利なコーディングテクニックです。
ちなみに、CompositeDisposable の実装例はいくつかありますが、基本的な目的は同じです。
CompositeDisposable だけでも便利なのですが、DI パターンを利用するとコンテナ経由で VM に CompositeDisposable のインスタンスを簡単に共有することができるので、とても便利だなと思いました。
今回の例では prism の Unity コンテナを利用しました。
使い方の例
- Test1ViewModel
- Test2ViewModel
- MainWindowViewModel
以上の3つの VM で Dispose(終了時の処理)を実行したいとします。まず、コンテナに CompositeDisposable を登録します。App に記述して、登録と破棄 (dispose) を記述することができます。
public partial class App { private CompositeDisposable Disposables = new CompositeDisposable(); protected override Window CreateShell() => Container.Resolve<Views.MainWindow>(); protected override void RegisterTypes(IContainerRegistry containerRegistry) { // Libraries containerRegistry.RegisterInstance(Disposables, "CompositeDisposable"); } protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { moduleCatalog.AddModule<SampleModule>(); } protected override void OnExit(ExitEventArgs e) { base.OnExit(e); Disposables.Dispose(); } }
これで、(Prism の場合)プログラム終了時に OnExit が実行され、Disposables
に登録された廃棄メソッドをまとめて呼び出すことができます。コードを実行する箇所もわかりやすいし、役割も明確。とても簡単ですね。(混同しちゃダメなのは、CompositeDisposable は Prism の機能ではないので自由に利用できます)
次に VM に Dispose で実行する内容をどのようにコーディングするかというと、こんな感じです。
public class MainWindowViewModel : BindableBase { public MainWindowViewModel(IUnityContainer container) { var disposable = container.Resolve<CompositeDisposable>("CompositeDisposable"); disposable.Add(() => { Debug.WriteLine($"Called from {nameof(MainWindowViewModel)}."); }); } }
コンテナから目当ての CompositeDisposable を取り出して、リソースを解放する匿名メソッドを Add します。
CompositeDisposable を DI コンテナ経由で各 VM に伝搬するのは、便利だというのは見ての通りですね。しかし、singleton パターンでも同様のことができるのではないか、と考えるかもしれないです。
実際、singleton パターンでもやれないことはないと思います。しかし、例のように複数の複数のプロジェクトに VM を持つ場合は、singleton パターンをどのプロジェクトに作るのか困ると思います。(拡張性としても困ると思います)
また、xUnit などのテストプロジェクトを作ったとき、VM のモックをテストしづらい状況に陥いる恐れがあります。(コンストラクタの中で singleton のインスタンスから CompositeDisposable を取得することになる可能性が高い)
こんな感じで CompositeDisposable をどのようにして活用すると、より便利に扱うことができるのか(CompositeDisposable のインスタンスをどのように欲しいコードの箇所に渡すのか)もポイントだと思います。
この例だとアプリ全体の終了時にのみ CompositeDisposable を利用していますが、Unity だとシーンの切り替わりのように、リソースを解放するタイミングは色々あると思います。
サンプル
GitHub にサンプルのコードを公開しています。
アプリ終了のタイミングで、サンプルの出力を出します。