【WPF Prism】 DIの遅延解決

## はじめに

最近、ReactiveUIの[Dependency Injection](https://www.reactiveui.net/docs/handbook/dependency-inversion/)(Splat)について調べて[zennに書いた](https://zenn.dev/t13801206/articles/2203-avalonia-comparison-di)

Splatでは、「オンデマンドでインスタンスを生成する方法」が使える

> Splat supports on-demand new'ing, constant and lazy registration of dependencies.

```cs
// デリゲートを登録
Locator.CurrentMutable.Register(() => new Person(), typeof(IPerson));

// 名前解決
var person = Locator.Current.GetService<IPerson>();
```

必要になるまでインスタンスを生成しないようコントロールできるので、メモリ節約の面で利点がある

### この記事は

先述のSplatによるインスタンス遅延解決をPrismで試す

- .NET Core 3.1
- Prism.Unity 8.0.0.1909

## Prism Library の遅延解決

Prism 8 以降は遅延解決の機能を標準実装しているらしい

[Lazy Resolution - Prism Documentation](https://prismlibrary.com/docs/dependency-injection/registering-types.html#lazy-resolution)

### ふつうのコンストラクタインジェクション

まずは比較のために、ふつうの方法

`MainWindowViewModel`が呼ばれたタイミングで、引数の`person`が生成されている

```cs
public class MainWindowViewModel : BindableBase
{
    IPerson person;

    public MainWindowViewModel(Person person)
    {
        Debug.WriteLine("1");
        this.person = person;
        Debug.WriteLine("2");
    }
}

/* output
Person Constructor Called!!
1
2
*/
```

メンバー変数`this.person`へ代入する以前にコンストラクターが生成されて``Person Constructor Called!!`が表示されている

### 遅延解決をつたったコンストラクターインジェクション

`MainWindowViewModel`の引数には[Lazy＜T>](https://docs.microsoft.com/ja-jp/dotnet/api/system.lazy-1)クラスが渡される

インスタンス化するには`.Value`をつける

```cs
public MainWindowViewModel(Lazy<Person> factory)
{
    Debug.WriteLine("1");
    var f = factory;
    Debug.WriteLine("2");
    this.person = f.Value;
    Debug.WriteLine("3");
}
/* output
1
2
Person Constructor Called!!
3
*/
```

メンバー変数`this.person`へ代入するタイミングでインスタンスが生成されて`Called!!`メッセージが表示されている

### ・・・ここで間違いに気づく

コンストラクターインジェクションをするならば、`***ViewModel`の引数に**インターフェイス`IPerson`を渡さねばらない**

本来ここまで書いた`ViewModel`のコンストラクターの引数カッコ内には`IPerson`が存在するはず

```cs
// 遅延解決ならこう書くし
public MainWindowViewModel(Lazy<IPerson> factory)

// ふつうのコンストラクターインジェクションならこう書く
public MainWindowViewModel(IPerson person)
```

そもそも`App`クラスの`void RegisterTypes(IContainerRegistry containerRegistry)`メソッドで登録作業をしていなかった

しかし、見た感じは想定通りに動いていた・・なぜ？

#### ViewModelの引数にインターフェイスを渡す

試しにDI登録していない状態で引数をインターフェイスにしたらどうなるのか確認する

##### `Lazy<T>`を使った遅延解決の場合

「`IPerson`型のコンストラクターがありません」エラー

```cs
IPerson person;

public MainWindowViewModel(Lazy<IPerson> factory)
{
    this.person = factory.Value; // ここで例外発生
}
```

> Unity.ResolutionFailedException: 'Resolution failed with error: No public constructor is available for type FullApp.IPerson.

##### `Lazy<T>`を使わない場合

内部例外2に同じ例外メッセージが書かれている

```cs
public MainWindowViewModel(IPerson person) // ここで例外発生
{
}
```

> Message='プロパティ 'Prism.Mvvm.ViewModelLocator.AutoWireViewModel' の Set で例外がスローされました。

> 内部例外 1:
> ContainerResolutionException: An unexpected error occurred while resolving 'FullApp.ViewModels.MainWindowViewModel'
> 
> 内部例外 2:
> ResolutionFailedException: Resolution failed with error: No public constructor is available for type FullApp.IPerson.
>
> 内部例外 3:
> InvalidOperationException: No public constructor is available for type FullApp.IPerson.
> 
> 内部例外 4:
> InvalidRegistrationException: 

### `App`クラスでDI登録する

`containerRegistry.RegisterSingleton<IPerson, Person>();`

想定通りの結果になった（上記で示したとおりのコンソール出力だった）

ほぼ内容が同じで重複するので掲載は割愛する

## DI登録しなくてもインスタンスがViewModelに渡る仕組み

[ViewModelLocator - Prism Library](https://prismlibrary.com/docs/viewmodel-locator.html)

ここを読んだだけではよくわからず、OSSなのでソースを見てみる

長くなる＆本題から外れるのでここまで　→[つづき](https://qiita.com/miya416/items/7bacfb603cb314b61627)

## おわりに

Prismライブラリの遅延解決は簡単だった

また、
ReactiveUIのSplatでは**登録時にあらかじめ遅延させるか決める**必要があったが
Prism.Unityでは**呼び出す側で遅延する／しないを決められる**

という違いがあることがわかった