imog

主にUnityとかの事を書いています

ZenjectのConstructionMethodたち

ZenjectでBindするとき、そのインスタンスをどのように用意するかという設定が必要です。その場で初期化するのか、既存のインスタンスを引っ張ってくるのかなど。これらはコンストラクションメソッドと呼ばれており、READMEに全部書かれています。

github.com

割と数が多いのでまとめてみました。間違っているものがあればコメントなどもらえるとありがたいです。

FromNew()

コンストラクタを呼び出します。特にConstruction Methodを定義しない場合デフォルトでFromNewが呼ばれます

Container.Bind<Foo>().FromNew().AsCached();
Container.Bind<Foo>().AsCached(); // FromNew()は省略できる

コンストラクタが複数あった場合、最初に引数なしコンストラクタを呼ぼうとします。見つからなかった場合引数ありコンストラクタを呼びます。

FromInstance(T instance)

既存のインスタンスを渡してBindします。

Container.Bind<Foo>().FromInstance(new Foo());
Container.BindInstance(new Foo());  // 同じ挙動

ユニークなインスタンスを明示的に渡すので、ScopeをAsTransientにしても効果はありません。実質強制的なAsCached指定?

FromMethod(Func<T> method)

FromMethod(Func<InjectContext, T> method)

インスタンスの生成をメソッドに移譲します。

private Foo GetFoo() => new Foo();

Container.Bind<Foo>().FromMethod(GetFoo).AsCached();
Container.Bind<Foo>().FromMethod(() => new Foo()).AsCached();
Container.Bind<Foo>().FromMethod(injectContext => new Foo()).AsCached();

InjectContext はInjectに関するメタ情報が詰まったクラスですが、基本的には使うことはないと思います。

FromMethodMultiple(Func<IEnumerable<T>> method)

FromMethodMultiple(Func<InjectContext, IEnumerable<T>> method)

配列やリストの生成をメソッドに移譲します。

private IEnumerable<Foo> GetFoos() => new List<Foo>();

Container.Bind<Foo>().FromMethodMultiple(GetFoos).AsCached();
Container.Bind<Foo>().FromMethodMultiple(injectContext => new List<Foo>()).AsCached();

複数形である以外は FromMethod と同様です。

FromFactory<IFactory<T>>()

任意のIFactoryクラスで生成します。

public class Foo {
    public class Factory : PlaceholderFactory<Foo> { }
}

Container.Bind<Foo>().FromFactory<Foo.Factory>();

Zenject Factoryパターンについてはこちらも合わせてどうぞ。

adarapata.hatenablog.com

FromIFactory(Action<ConcreteBinderGeneric<IFactory<T>>> factoryBindGenerator)

カスタムファクトリで生成します。

public class Foo {
    public Foo(string hoge) { }

    public class Factory : IFactory<Foo> {
        private string _arg;
        public Factory(string arg) {
            _arg = arg;
        }
        public Foo Create() => new Foo(_arg);
    }
}
Container.Bind<Foo>().FromIFactory(x => x.To<Foo.Factory>().WithArguments("hoge")).AsCached();

FromFactoryとの違いは、Factory生成の自由度です。 FromFactoryは暗黙的にFromNew()で生成されるためFactory自体の生成方法に制限がかかりますが、こちらは自由に定義できます。引数つけてもいいしSubContainerからとってきてもよい。

公式ドキュメントだとScriptableObjectからFactoryを引っ張ったりしてます。

class FooFactory : ScriptableObject, IFactory<Foo>
{
    public Foo Create()
    {
        // ...
        return new Foo();
    }
}
// ScriptableObjectから生成したFooFactoryからFooを生成してBindする
Container.Bind<Foo>().FromIFactory(x => x.To<FooFactory>().FromScriptableObjectResource("FooFactory")).AsSingle();

FromComponentInNewPrefab(UnityEngine.Object prefab)

InstantiateしたPrefabからComponentをBindします。

Container.Bind<FooBehavior>().FromComponentInNewPrefab(fooPrefab).AsCached();

内部的にはGetComponentInChildrenで探すので、複数あった場合は先に見つかった方をBindします。

FromComponentsInNewPrefab(UnityEngine.Object prefab)

FromComponentInNewPrefabの複数版です。

Container.Bind<FooBehavior>().FromComponentsInNewPrefab(fooPrefab).AsCached();

こちらはGetComponentsInChildrenで探します。

FromComponentInNewPrefabResource(string path)

パスからPrefabをInstantiateしてBindします。

Container.Bind<FooBehavior>().FromComponentInNewPrefabResource("some/foo").AsCached();

内部的にはResources.Loadが走ります

FromComponentsInNewPrefabResource(string path)

FromComponentInNewPrefabResourceの複数版です。

Container.Bind<FooBehavior>().FromComponentsInNewPrefabResource("some/foo").AsCached();

FromNewComponentOnNewGameObject()

空のGameObjectを生成してAddComponentしたものをBindします。

Container.Bind<FooBehavior>().FromNewComponentOnNewGameObject().AsCached();

FromNewComponentInNewPrefab(UnityEngine.Object prefab)

PrefabをInstantiateしてAddComponentしたものをBindします。

Container.Bind<FooBehavior>().FromNewComponentInNewPrefab(fooPrefab).AsCached();

FromNewComponentInNewPrefabResource(string path)

パスからPrefabをInstantiateしてAddComponentしたものをBindします。

Container.Bind<FooBehavior>().FromNewComponentInNewPrefabResource("some/foo").AsCached();

パスから読み込むのでResources.Loadが呼ばれます。

FromNewComponentOn(GameObject gameObject)

FromNewComponentOn(Func<InjectContext, GameObject> gameObjectGetter)

既存のGameObjectにAddComponentしたものをBindします。

Container.Bind<FooBehavior>().FromNewComponentOn(fooGameObject);

FromNewComponentSibling()

依存しているComponentと同階層にAddComponentしたものをBindします。

public class BarBehavior : MonoBehavior {
    [Inject]
    private FooBehavior _foo;
}

Container.Bind<FooBehavior>().FromNewComponentSibling();

この場合、BarBehaviorがアタッチされているGameObjectにFooBehaviorがAddComponentされます。 この特性から、Injectされる側がComponentでないと使えません。また、複数依存しているオブジェクトがあった場合それぞれの同階層にAddComponentされます。よって強制的にAsTransient指定されるイメージです。AsSingleやAsCachedを明示的に呼んでもエラーは出ませんが適用はされません。

FromNewComponentOnRoot()

現在のContextと同じ階層にAddComponentしたものをBindします。

Container.Bind<FooBehavior>().FromNewComponentOnRoot().AsCached();

例えばSceneContextでInstallerを呼んだ場合はSceneContextと同じ階層にFooBehaviorが作成されます。基本的にSceneContextで使うことはなく、GameObjectContextなどのSubContainerで使うのがメインになるでしょう。

FromResource(string path)

Resourceディレクトリのファイルを読み込んでBindします。

Container.Bind<Texture>().FromResource("some/texture").AsCached();

Resources.Loadでロードできるファイルは全部対応してます。

FromScriptableObjectResource(string path)

ResourceディレクトリのScriptable Objectを読み込んでBindします。

Container.Bind<FooScriptable>().FromScriptableObjectResource("some/foo").AsCached();

直接元データをBindするので、動的に値を更新するとファイルが書き換わってしまうので注意

FromNewScriptableObjectResource(string path)

ResourceディレクトリのScriptable Objectから読み込んでコピーしたインスタンスをBindします。

Container.Bind<FooScriptable>().FromNewScriptableObjectResource("some/foo").AsCached();

元データを書き換えたくない場合はこちらを使いましょう。

FromComponentInHierarchy()

シーンのヒエラルキーを辿ってコンポーネントを探してBindします。

Container.Bind<FooBehavior>().FromComponentInHierarchy();

GetComponentOnChildren で検索します。ParentContractがある場合親のシーンも辿ると書いてるけど検証できていない・・

FromComponentsInHierarchy()

FromComponentInHierarchyの複数版です。

Container.Bind<FooBehavior>().FromComponentInHierarchy()

こちらはGetComponentsOnChildrenで検索します。

FromComponentSibling()

依存しているComponentの同階層を検索してBindします。

public class BarBehavior : MonoBehavior {
    [Inject]
    private FooBehavior _foo;
}

Container.Bind<FooBehavior>().FromComponentSibling();

上記の場合、BarBehaviorと同じ階層にアタッチされているFooBehaviorをInjectします。 この特性から、Injectされる側(今回はFooBehavior)がComponentでないと使えません。

内部的にはGetComponentを呼んでいます。

FromComponentsSibling()

FromComponentSiblingの複数版です。

Container.Bind<FooBehavior>().FromComponentsSibling();

内部的にはGetComponentsを呼んでいます。

FromComponentInParents()

依存しているComponentの同階層と、親を検索してBindします。

Container.Bind<FooBehavior>().FromComponentInParents();

内部的にはGetComponentInParentが呼ばれています。

FromComponentsInParents()

FromComponentInParentsの複数版です。

FromComponentInChildren()

依存しているComponentの同階層と子を検索してBindします。

Container.Bind<FooBehavior>().FromComponentInChildren();

内部的にはGetComponentInChildrenが呼ばれています。

FromComponentsInChildren()

FromComponentInChildrenの複数版です。内部的にはGetComponentInChildrenが呼ばれています。

FromResolve()

Containerから検索して再度Bindします。基本的に使うことはありませんが、インタフェースを別のインタフェースにBindしたいときなどに使えます。

public interface IFoo
{
}

public interface IBar : IFoo
{
}

public class Foo : IBar
{
}

Container.Bind<IFoo>().To<IBar>().FromResolve();
Container.Bind<IBar>().To<Foo>();

上記の場合、IBarとしてBindされたFooをIFooとしてもBindします。

FromResolveAll()

FromResolveの複数版です。

FromResolveGetter<T>(Func<T, T2> getter)

Containerから取得したオブジェクトから取得してBindします。

Container.Bind<Foo>().FromResolveGetter<Bar>(bar => bar.GetFoo());

階層的な構造になっている場合は結構便利です。

FromResolveAllGetter<T>(Func<T, T2> getter)

FromResolveGetterの複数版です。

FromSubContainerResolve()

SubContainerから検索してBindします。使う場合SubContainerを作成する必要があるので、このメソッドの後にさらにSubContainerのConstruction Methodを呼ぶことになります。

FromSubContainerResolveAll()

FromSubContainerResolve()の複数版です。

SubContainerのConstruction Method

ByNewPrefabMethod(UnityEngine.Object prefab, Action<DiContainer> installerMethod)

Prefabからインスタンスを生成し、それにSubContainerを持たせてメソッドで初期化します。

public GameObject SubContainerPrefab;
public override void InstallBindings()
{
    Container.Bind<Foo>().FromSubContainerResolve()
            .ByNewPrefabMethod(SubContainerPrefab, InstallSubContainer).AsCached();
}

private void InstallSubContainer(DiContainer container)
{
    container.Bind<Foo>().AsCached();
}

この方法で生成されたインスタンスは自動でGameObjectContextがアタッチされます。なのでどんなPrefabでもSubContainerを持つことができます。

ByNewPrefabInstaller<TInstaller>(UnityEngine.Object prefab)

Prefabからインスタンスを生成し、それにSubContainerを持たせてInstallerで初期化します。

Container.Bind<Foo>().FromSubContainerResolve().ByNewPrefabInstaller<FooInstaller>(SubContainerPrefab);

class FooInstaller : Installer
{
    public override void InstallBindings()
    {
        Container.Bind<Foo>();
    }
}

基本的な挙動はByNewPrefabMethodと同じで、SubContainerへのBindがInstallerクラスに変わっただけです。

ByNewPrefabResourceMethod(string resourcePath, Action<DiContainer> installerMethod)

Resourcesからインスタンスを生成し、それにSubContainerを持たせてメソッドで初期化します。

Container.Bind<Foo>().FromSubContainerResolve().ByNewPrefabResourceMethod("Path/To/Prefab", InstallFoo);

void InstallFoo(DiContainer subContainer)
{
    subContainer.Bind<Foo>();
}

Resources.Loadが走ります。

ByNewPrefabResourceInstaller<TInstaller>(string resourcePath)

Resourcesからインスタンスを生成し、それにSubContainerを持たせてInstallerでBindします。

Container.Bind<Foo>().FromSubContainerResolve().ByNewPrefabResourceInstaller<FooInstaller>("Path/To/MyPrefab");

class FooInstaller : Installer<FooInstaller>
{
    public override void InstallBindings()
    {
        Container.Bind<Foo>();
    }
}

TInstaller : InstallerBaseなので、Installer<T>を継承したもののみ使えます。 MonoInstallerはできないので注意

ByNewGameObjectInstaller<TInstaller>()

空のGameObjectを生成し、それにSubContainerを持たせてInstallerでBindします。

Container.Bind<Foo>().FromSubContainerResolve().ByNewGameObjectInstaller<FooInstaller>();

class FooInstaller : Installer<FooInstaller>
{
    public override void InstallBindings()
    {
        Container.Bind<Foo>();
    }
}

後述のByInstaller<TInstaller>とほぼ同じ動きをしますが、空のGameObjectにGameObjectContextがアタッチされているのがポイントです。IInitializableITickableなども適切にBindされるし、GameObjectを破棄すればSubContainerは破棄されます。

ByNewGameObjectMethod(Action<DiContainer> installerMethod)

空のGameObjectを生成し、それにSubContainerを持たせてメソッドでBindします。

Container.Bind<Foo>().FromSubContainerResolve()
                     .ByNewGameObjectMethod(InstallSubContainer);

void InstallSubContainer(DiContainer subContainer)
{
    subContainer.Bind<Foo>();
}

メソッドでBindする以外はByNewGameObjectInstallerと同じです。

ByMethod(Action<DiContainer> installerMethod)

メソッドでSubContainerを初期化します。

Container.Bind<Foo>().FromSubContainerResolve()
                     .ByMethod(InstallSubContainer);

void InstallSubContainer(DiContainer subContainer)
{
    subContainer.Bind<Foo>();
}

こちらはITickable IInitializable IDisposableといったインタフェースを適切にBindできません。それらを処理するKernelクラスがいないからです。もし必要ならWithKernel()を合わせて呼びます。

Container.Bind<Foo>().FromSubContainerResolve().ByMethod(InstallSubContainer).WithKernel();
ByNewGameObjectInstaller<TInstaller>()

InstallerでSubContainerを初期化します。

Container.Bind<Foo>().FromSubContainerResolve().ByInstaller<FooInstaller>();

class FooInstaller : Installer<FooInstaller>
{
    public override void InstallBindings()
    {
        Container.Bind<Foo>();
    }
}

こちらもByMethod同様に、Kernel不在のためITickableを処理できません。WithKernel()を呼ぶとよいでしょう。

ByNewContextPrefab(UnityEngine.Object prefab)

GameObjectContextをアタッチしたPrefabでSubContainerを初期化します。

Container.Bind<Foo>().FromSubContainerResolve().ByNewContextPrefab(MyPrefab);

class FooFacadeInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        Container.Bind<Foo>();
    }
}

もちろんPrefab側にはGameObjectContextがアタッチされている必要があるので注意。

ByNewContextPrefabResource(string resourcePath)

GameObjectContextをアタッチしたPrefabをLoadしてSubContainerを初期化します。

Container.Bind<Foo>().FromSubContainerResolve().ByNewContextPrefabResource("Path/To/MyPrefab");

Resources.Loadが呼ばれます。

ByInstance(DiContainer subContainer)

直接SubContainerとなるDiContainerを渡します。

おわりに

多すぎる。

追記

この記事を書くにあたってドキュメント見てたら間違いっぽいのを見つけたのでPR出した。 github.com

ブログ書くのはいいぞ。