Prism.Unity のライフサイクル調査

Prism.Unity の環境で アプリ終了時に DIコンテナ の登録インスタンスを Dispose したく、UnityContainer のライフサイクル について調査した記事です。

ゲームエンジンの "unity" とは全く関係ありません。

本記事の大半は Prism.Unity 限定ではなく、UnityContainer で共通の内容です。

## はじめに

私は業務で稀に WPF アプリを作る機会があり、その際は Prism + UnityContainer をベースに開発しています。

Prism では UnityContainer 以外にもいくつかの DIライブラリ（Autofac, Ninject, DryIoc）  をサポートおり、特別 UnityContainer に拘っていないのですが「Microsoft が関わっていた」との理由から なんとなーくで選んでいます。

と言っても私の場合は、ModelクラスのいくつかをDIコンテナに登録する程度のライトな用途なので、Prism が提供してくれているインターフェイス越しの操作で十分でした。

先日、アプリ終了時に DIコンテナに登録したインスタンスを Dispose したい 機会があり、Prism提供の共通インターフェイス に物足りなさを感じましたので、正月休みを利用して UnityContainer の ライフサイクル を調査してみました。



## Prismでの生DIコンテナの取得

かずき先生が [Prism 7.x で DI コンテナ固有の機能を使いたい](https://blog.okazuki.jp/entry/2019/02/05/094546) にて詳しくまとめて下さっていますので、そちらに案内させて頂きます。毎度お世話になっております🙇‍♂️

``` C#
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    IUnityContainer unityContainer = containerRegistry.GetContainer();
}
```



## DIコンテナへの登録方法

UnityContainer への登録方法は大きく分けて 3つ あります。

1. 型からの登録
2. Factoryを使用した登録
3. インスタンスの登録

何れもDIコンテナの一般的な概念だと思いますので詳細は割愛します。

今回は最もベタな登録方法だと思われる 『1.型からの登録』のライフサイクルのみを調査しました。



## 型登録時のライフライクル

型登録時のライフサイクルは、`RegisterType<T>()` の引数 `ITypeLifetimeManager lifetimeManager` から指定できます。

以降は  `ITypeLifetimeManager` の取得メソッドを提供する `static class TypeLifetime` のメソッド名でまとめてみました。([TypeLifetimeクラスのソースコード](https://github.com/unitycontainer/abstractions/blob/2b99c9686bc01c3ef22ef294ec3afd558b4cb994/src/Extensions/Lifetime/TypeLifetime.cs))

丸括弧は取得メソッドから返却される実クラス名です。



それでは順に見ていきましょう。



### 1. Transient (TransientLifetimeManager)

- `Resolve<T>()` の呼び出しの度に、新しいインスタンスが生成されて返却されます。

- `RegisterType<T>()` のデフォルトのライフタイムで、ライフタイムを管理しないことと同義です。

  ```C#
  // 明示的に TypeLifetime.Transient を渡さないようにましょう（公式より）
  container.RegisterType<Foo>();
  container.RegisterType<IService, Service>();
  
  var s1 = container.Resolve<IService>();
  var s2 = container.Resolve<IService>();
  Console.WriteLine(ReferenceEquals(s1, s2));	// "false"
  ```

  

### 2. Singleton (SingletonLifetimeManager)

- ガチのシングルトンです。

- 後続の `Resolve<T>()` の呼び出し や コンストラクタインジェクションなどのインスタンス挿入で 同一インスタンスが返却されます。

- `RegisterType<T>()` の登録結果は コンテナの親子ツリー全体に反映されます。（子コンテナ に登録した場合、ルート親コンテナに登録されます。）

- コンテナが生成したインスタンスの参照を保持します。インスタンスが `IDisposable` を継承している場合、コンテナ自体の `Dispose()` 時に保持インスタンスを Dispose してくれます。

- 登録済みの型を再登録した場合は、新しい登録が優先されます。

  ```C#
  container.RegisterType<IService, DisposableService>(TypeLifetime.Singleton);
  
  var s1 = container.Resolve<IService>();
  var s2 = container.Resolve<IService>();
  Console.WriteLine(ReferenceEquals(s1, s2));	// "true"
  
  container.Dispose();	// s1,s2 を Dispose してくれる
  ```



### 3. ContainerControlled / PerContainer (ContainerControlledLifetimeManager)

- Singleton と同じ挙動です。

- こちらが Singleton の基底クラスであり、Singletonクラスは実装がほぼ空なので ([ソースコード](https://github.com/unitycontainer/abstractions/blob/2b99c9686bc01c3ef22ef294ec3afd558b4cb994/src/Lifetime/Managers/SingletonLifetimeManager.cs))、迷った場合はこちらを使用しましょう。

- ちなみに、`RegisterSingleton<T>()` のライフタイム にも Singleton ではなく、こちらが指定されています。([ソースコード](https://github.com/unitycontainer/abstractions/blob/2b99c9686bc01c3ef22ef294ec3afd558b4cb994/src/Extensions/Container/UnityContainer.cs#L191))

  ```C#
  container.RegisterType<IService, Service>(TypeLifetime.PerContainer);
  
  var s1 = container.Resolve<IService>();
  var s2 = container.Resolve<IService>();
  Console.WriteLine(ReferenceEquals(s1, s2));	// "true"
  
  container.Dispose();	// s1,s2 を Dispose してくれる
  ```

  

### 4. PerContainerTransient (ContainerControlledTransientManager)

- Transient と同様に `Resolve<T>()` の呼び出しの度に、新しいインスタンスが生成されて返却されます。

- Transient の違いとして（PerContainer と同様に）コンテナが生成したインスタンスの参照を保持します。

  ```C#
  container.RegisterType<IService, Service>(TypeLifetime.PerContainerTransient);
  
  var s1 = container.Resolve<IService>();
  var s2 = container.Resolve<IService>();
  Console.WriteLine(ReferenceEquals(s1, s2));	// "false"
  
  container.Dispose();	// s1,s2 を Dispose してくれる
  ```

  

### 5. Hierarchical / Scoped  (HierarchicalLifetimeManager)

- 同一のコンテナであれば PerContainer と同じ挙動です。（シングルトン、参照保持）

- PerContainer との違いとして、親コンテナ と 各子コンテナ で情報が共有されません。`Resolve<T>()` を呼び出したコンテナに対応するインスタンスが返却されます。

  ```C#
  container.RegisterType<IService, Service>(TypeLifetime.Hierarchical);
  var s1 = container.Resolve<IService>();
  var s2 = container.Resolve<IService>();
  Console.WriteLine(ReferenceEquals(s1, s2));	// "true"
  
  using (var child = container.CreateChildContainer())
  {
      child.RegisterType<IService, Service>(TypeLifetime.Hierarchical);
      var s3 = child.Resolve<IService>();
      Console.WriteLine(ReferenceEquals(s1, s3));	// "false"
  } // s3 を Dispose してくれる
  ```



### 6. PerResolve (PerResolveLifetimeManager)

- 1回の解決呼び出し中だけインスタンスへの参照を保持します。

- 文字での説明が難しいのですが、下記の依存関係の場合に ClassC を生成した場合、"ClassB 内の ClassA"  と "ClassC 内の ClassA"  を同一インスタンスにしてくれます。

  ```C#
  // クラスの依存関係
  class ClassA {}
  class ClassB {
      public ClassB(ClassA a) => _a = a;
  }
  class ClassC {
      public ClassC(ClassA a, ClassB b) => (_a, _b) = (a, b);
  }
  ```

  ```C#
  container.RegisterType<ClassA>(TypeLifetime.PerResolve);
  container.RegisterType<ClassB>();
  container.RegisterType<ClassC>();
  
  var c = container.Resolve<ClassC>();
  Console.WriteLine(ReferenceEquals(c.b.a, c.a));	// "true"
  ```

- 参照の保持は1回の解決呼び出し中だけなので、異なる ClassC の ClassA は別インスタンスとなります。

  ```C#
  var c1 = container.Resolve<ClassC>();
  var c2 = container.Resolve<ClassC>();
  Console.WriteLine(ReferenceEquals(c1.a, c2.a));	// "false"
  ```

  

### 7. PerThread (PerThreadLifetimeManager)

- スレッドごとのシングルトンです。

- 参照は保持されません。コンテナを `Dispose()` しても各インスタンスは Dispose されません。

  ```C#
  container.RegisterType<IService, Service>(TypeLifetime.PerThread);
  
  var s1 = container.Resolve<IService>();
  var s2 = await Task.Run(() => container.Resolve<IService>());
  Console.WriteLine(ReferenceEquals(s1, s2));	// "false"
  ```



## Prism の DIコンテナ インターフェイス

Prism の DIコンテナ インターフェイスである `IContainerRegistry` を使用して登録した場合に、UnityContainer のどの Lifetime が使用されるかを整理しました。[ソースコード](https://github.com/PrismLibrary/Prism/blob/1046aee5cca5b40e073f1dbc06c09ed7a5506dc7/src/Containers/Prism.Unity.Shared/UnityContainerExtension.cs)

| IContainerRegistry の登録メソッド | UnityContainer Lifetime |
| --------------------------------- | ----------------------- |
| Register\<T>()                    | Transient               |
| RegisterSingleton\<T>()           | ContainerControlled     |
| RegisterScoped\<T>()              | Hierarchical            |



ここまでのライフサイクル調査のおかげで、`RegisterSingleton<T>()` と `RegisterScoped<T>()` の場合、**アプリ終了時に DIコンテナ自体 を `Dispose()` することで**、参照を保持しているインスタンス達 を Dispose してくれることが分かりました。

少しやっつけですが、以下の実装で良いかと思われます。

```C#
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterSingleton<IService, Service>();
    containerRegistry.RegisterScoped<IReader, Reader>();

    // Exit時に Service と Reader も Dispose される。
    this.Exit += (_, _) => containerRegistry.GetContainer().Dispose();
}
```



`Register<T>()` （Transient）の場合は DIコンテナが何もしてくれないので、`Resolve<T>()` でインスタンスを取得した人が Dispose を行う必要があります。



## おわりに

Prism.Unity の環境で アプリ終了時に DIコンテナ の登録インスタンスを Dispose するため、UnityContainer のライフサイクル について調査しました。

もう少し詳しく調べたい部分もあり 後ろ髪を引かれましたが、年内に完了させてスッキリしたく公開しました。それでは良いお年を🎍



## 確認環境

VisualStudio 2019 16.8.3

.NET 5.0 + C# 9.0

Prism.Unity 8.0.0.1909

Unity.Container 5.11.9

https://github.com/hsytkm/UnityLifetimeComparisons



## 参考にさせて頂いたページ

[Lifetime - UnityContainer](http://unitycontainer.org/tutorials/lifetime/lifetime.html)

[Unity Container - GitHub](https://github.com/unitycontainer)

[Disposing needed in Unity? - stackoverflow](https://stackoverflow.com/questions/13581655/disposing-needed-in-unity)

[PRISM/Unity IDisposable - stackoverflow](https://stackoverflow.com/questions/16459512/prism-unity-idisposable)

[Prism 7.x で DI コンテナ固有の機能を使いたい](https://blog.okazuki.jp/entry/2019/02/05/094546)