InjectとInjectLocal attribute
Zenjectのソース読んでたらInjectLocal
というAttributeが内部にあったのでなんだろうと調べたメモ。実用的なやつではない。
どのContainerからInjectされるのか
そもそも、Bindされたオブジェクトはどこから依存しているオブジェクトをもらえるのでしょう。これは基本的には自身がBindされているContainerになります。次のコードを見てみましょう。Fooに依存しているBarを生成するコードです。
public class Foo { public ContainerName { get; private set; } public Foo(string containerName) { ContainerName = containerName; } } public class Bar { [Inject] Foo _foo; public void DoBar() => Debug.Log(_foo.ContainerName); }
public class FooInstaller : MonoInstaller { public override void InstallBindings() { // 現在のContainerとProjectContextの両方でFooを用意しておく ProjectContext.Instance.Container.Bind<Foo>().AsCached().WithArguments("ProjectContext"); Container.Bind<Foo>().AsCached().WithArguments("Scene"); var subContainer = Container.CreateSubContainer(); subContainer.Bind<Foo>().AsCached().WithArguments("Sub"); // 現在のContainerにBarをBindする Container.Bind<Bar>(); Container.Resolve<Bar>().DoBar(); } }
=> Scene
この場合、Barに渡されるFooインスタンスは同じContainerに所属しているインスタンスを渡されます。 同じContainerにFooが存在しない場合、親のContainerを探しに行きます。親も持っていなかったらさらにその親・・と最終的にProjectContextまで辿っていくことになります。逆に自身のSubContainerを見に行くなど下っていくことはありません。
上を辿っていくのが基本的なルールとして、どのくらいまで辿っていくのかという部分はAttributeやOptionで制御できたりします。
InjectLocal
InjectLocal
をつけると親を辿りません。
public class Bar { // 同じContainerからしか探さない [InjectLocal] Foo _foo; public void DoBar() => Debug.Log(_foo.ContainerName); } public class FooInstaller : MonoInstaller { public override void InstallBindings() { // 現在のContainerとProjectContextの両方でFooを用意しておく ProjectContext.Instance.Container.Bind<Foo>().AsCached().WithArguments("ProjectContext"); // BarがいるContainerにはFooをBindしない //Container.Bind<Foo>().AsCached().WithArguments("Scene"); var subContainer = Container.CreateSubContainer(); subContainer.Bind<Foo>().AsCached().WithArguments("Sub"); // 現在のContainerにBarをBindする Container.Bind<Bar>(); Container.Resolve<Bar>().DoBar(); } }
=> ZenjectException: Unable to resolve 'Foo' while building object with type 'Bar'. Object graph: Bar
自身の所属するContainerにはないのでそこでエラーが出てしまいます。
こんな感じで実は細かい設定ができたりします。Attributeの書き換えでもいいですし、Inject(Source = InjectSources.Local)
といったInjectSources
列挙体で渡すやりかたでも同じことができます。列挙体は以下の四つです。
- Any
- Local
- Parent
- AnyParent
InjectSources.Any
自身から探して、見つからなければ親を延々と辿っていきます。
通常のInject
がAnyに設定されているのでいつものやつです。
InjectSources.Local
上記の通り、自身のContainerだけを探して見つからなければエラーを吐きます。
InjectLocal
の設定と同じです。
InjectSources.Parent
自身の直接の親のContainerを探しに行きます。自身と同じContainerに存在していても参照されません。親が持っていない場合エラーを吐きます。
InjectSources.AnyParent
自身の親を延々と辿っていきます。自身のContainerは参照しません。
参考
InjectSourcesのテストコード見るとなるほど感あります。
ちなみにZenject内部ではKernel内部の各種Managerが混ざらないようにInjectLocalで分けてるっぽい。
サブコンテナ使ったりProjectContextでBindしてるインスタンスと同一のものが必要になったりとかしたら明示的に分けていいかもしれませんね。スコープが狭まることはいいこと。