sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

C# DI コンテナと CompositeDisposable の組み合わせ

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 にサンプルのコードを公開しています。

アプリ終了のタイミングで、サンプルの出力を出します。

参考