はじめに
最近、ReactiveUIのDependency Injection(Splat)について調べてzennに書いた
Splatでは、「オンデマンドでインスタンスを生成する方法」が使える
Splat supports on-demand new'ing, constant and lazy registration of dependencies.
// デリゲートを登録
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
ふつうのコンストラクタインジェクション
まずは比較のために、ふつうの方法
MainWindowViewModel
が呼ばれたタイミングで、引数のperson
が生成されている
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>クラスが渡される
インスタンス化するには.Value
をつける
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
が存在するはず
// 遅延解決ならこう書くし
public MainWindowViewModel(Lazy<IPerson> factory)
// ふつうのコンストラクターインジェクションならこう書く
public MainWindowViewModel(IPerson person)
そもそもApp
クラスのvoid RegisterTypes(IContainerRegistry containerRegistry)
メソッドで登録作業をしていなかった
しかし、見た感じは想定通りに動いていた・・なぜ?
ViewModelの引数にインターフェイスを渡す
試しにDI登録していない状態で引数をインターフェイスにしたらどうなるのか確認する
Lazy<T>
を使った遅延解決の場合
「IPerson
型のコンストラクターがありません」エラー
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に同じ例外メッセージが書かれている
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
ここを読んだだけではよくわからず、OSSなのでソースを見てみる
長くなる&本題から外れるのでここまで →つづき
おわりに
Prismライブラリの遅延解決は簡単だった
また、
ReactiveUIのSplatでは登録時にあらかじめ遅延させるか決める必要があったが
Prism.Unityでは呼び出す側で遅延する/しないを決められる
という違いがあることがわかった