ZenjectのSubContainerでつまづいた点

#はじめに
こんにちは、ZeniZeniです。
最近ZenjectのSubContainerというものを知り、試しに自分のプロジェクトでも扱ってみようとしたところめちゃめちゃつまづいてしまったので、そのつまづきポイントとその解決方法を共有したいと思います。

# SubContainerとは
こちらのいもさん(@adarapata )のスライドが非常にわかりやすいので、こちらを参照してください。
https://learning.unity3d.jp/3771/

# SubContainerでIInitializableやITickableを用いる
Zenjectの便利な機能の一つとして、MonoBehaviourのStartやUpdateのような機能をpure C#クラスでも用いることができるという機能があります。
実装は非常に簡単で、`IInitializable`や`ITickable`といったインターフェースを実装して、それらをBindするだけです。

```C#:

public class Ship : ITickable
{
    public void Tick()
    {
        // Perform per frame tasks
    }
    //どこかしらでBindする
    Container.Bind<ITickable>().To<Ship>().AsSingle();
}

```
この機能は非常に強力ですが、SubContainerで用いるにはいろいろと手間がかかります。

公式のドキュメントでは[こちら](https://github.com/svermeulen/Extenject/blob/master/Documentation/SubContainers.md#byinstaller-bymethod-with-kernel)に方法が書いてあります。

実装方法は主に二つ存在します。
## Kernelクラスを用いる
まずKernelクラスを継承したクラスを用意します。すでに存在しているクラスにわざわざ継承させるとクラスの責務がぼやけるので、別途新しく作ったほうがいいと思います。
そして継承したクラスを`BindInterfacesAndSelfTo`でBindすればそれだけで`IInitializable`、`ITickable`が機能します。

以下のコードは公式のドキュメント記載のコードに一部修正を加えたものです。

```C#:KernelInitializable.cs
using System;
using UnityEngine;
using Zenject;

public class GoodbyeHandler : IDisposable
{
    public void Dispose()
    {
        Debug.Log("Goodbye World!");
    }
}

public class HelloHandler : IInitializable
{
    public void Initialize()
    {
        Debug.Log("Hello world!");
    }
}

public class Greeter : Kernel
{
    public Greeter()
    {
        Debug.Log("Created Greeter!");
    }
}

public class TestInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        Container.BindInterfacesAndSelfTo<Greeter>()
           .FromSubContainerResolve().ByMethod(InstallGreeter).AsSingle();
    }

    void InstallGreeter(DiContainer subContainer)
    {
        subContainer.Bind<Greeter>().AsSingle();

        subContainer.BindInterfacesTo<GoodbyeHandler>().AsSingle();
        subContainer.BindInterfacesTo<HelloHandler>().AsSingle();
    }
}
```
Kernelクラスは`IInitializable`や`ITickable`といったインターフェースをすべて継承したクラスです。
KernelクラスにSubContainer内の`IInitializable`や`ITickable`がすべてInjectされていて、`Kernel`クラスがそれらすべてを呼び出すことでSubContainer内の`IInitializable`といったインターフェースが動作するようになっています。

GameObjectContextやSceneContextで`IInitializable`が動作するのは、Kernekクラスが自動的に追加されているからです。

## WithKernelメソッドを用いる(正しく動作しないケースがある)
Kernelクラスを用いる方法は新しくクラスを用意する必要があり最適とは言えません。そこでもう一つの方法として、WithKernelメソッドを用いる方法があります。

```C#:WithKernelInitializable.cs
using System;
using UnityEngine;
using Zenject;

public class GoodbyeHandler : IDisposable
{
    public void Dispose()
    {
        Debug.Log("Goodbye World!");
    }
}

public class HelloHandler : IInitializable
{
    public void Initialize()
    {
        Debug.Log("Hello world!");
    }
}

public class Greeter
{
    public Greeter()
    {
        Debug.Log("Created Greeter!");
    }
}

public class TestInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        Container.Bind<Greeter>()
           .FromSubContainerResolve().ByMethod(InstallGreeter).WithKernel.AsSingle();
    }

    void InstallGreeter(DiContainer subContainer)
    {
        subContainer.Bind<Greeter>().AsSingle();

        subContainer.BindInterfacesTo<GoodbyeHandler>().AsSingle();
        subContainer.BindInterfacesTo<HelloHandler>().AsSingle();
    }
}
```
こうすることでSubContainer内の`IInitializable`等のインターフェースが動作するようになります。
この方法ではKernelクラスを用いず、BindInterfacesAndSelfToも使わないので素晴らしいです。

…という風にドキュメントに書いてあるんですが、どうも正しく動作しないケースがあるみたいです。
実際上記のコードは正しく動作せず、"Hello World"は出力されません。
この不具合はissueでも上がっていますが、まだ解決されていません。
https://github.com/modesttree/Zenject/issues/574
https://github.com/svermeulen/Extenject/issues/13
自分でもいろいろ試してところ、うまく動作するときもあれば動作しないときもあり、その違いが全く分かっていないという状況です…
解決方法がわかり次第追記していきます。

# FromSubContainerResolveについて
ところでFromSubContainerResolveとはどういったコンストラクションメソッドでしょうか？
公式のドキュメントにはこう書いてあります。
> FromSubContainerResolve
Get ResultType by doing a lookup on a subcontainer. Note that for this to work, the sub-container must have a binding for ResultType. This approach can be very powerful, because it allows you to group related dependencies together inside a mini-container, and then expose only certain classes (aka "Facades") to operate on this group of dependencies at a higher level. For more details on using sub-containers, see this section. There are several ways to define the subcontainer:…

まぁ要するにSubContainer内を参照してそこからBindするというものです。
ではその参照するSubContainerはどう決めればいいのでしょうか？
FromSubContainerResolve続けてメソッドを書くのですが、そのうち最も使うであろう`ByInstaller`、`ByMethod`を見てみます。
> ByMethod - Initialize the subcontainer by using a method.
> ByInstaller - Initialize the subcontainer by using a class derived from Installer.

**Initialize** the subcontainerと書いてありますね。この二つだけでなく、現状すべてのSubContanerを決定づけるメソッドはSubContainerを新しく生成しています。
つまり何が言いたいかというと、同じメソッド、インストーラーを用いてFromSubContanerResolveを呼んでも、その時生成されて渡されるSubContainerは別物だということです。

## 同一のSubContanerからBindする

これの何が困るかというと、上記の`KernelInitializable.cs`でさらに`HalloHandler`も基のContainerにBindしたいときなどです。

```C#:
        Container.BindInterfacesAndSelfTo<Greeter>()
            .FromSubContainerResolve().ByMethod(InstallGreeter).AsSingle();
        Container.Bind<HelloHandler>()
            .FromSubContainerResolve().ByMethod(InstallGreeter).AsSingle();
```
と書いても、`Container.Bind<HelloHandler>()...`でBindしたHelloHandlerのIInitializableは呼ばれず、呼ばれるのは`Container.BindInterfacesAndSelfTo<Greeter>()...`でBindした別のHelloHandlerのIInitializableです。

`BindInterfacesAndSelfTo`は可変長引数ではないので、
`BindInterfacesAndSelfTo(typeof(Greeter),typeof(HelloHandler))`みたいな書き方はできません。
WithKernelを使う方法であるならば、Bindは可変長引数なので
`Bind(typeof(Greeter),typeof(HelloHandler))`と書くだけで終わりですがKernelを使う方法ではそうもいきません。
ではどうするかというと、Kernelクラスを継承したGreeterクラスにSubContainer内のHellorHandlerクラスをBindして、それを基のContainerにBindします。
具体的には、下記のコードのようにします。

```C#:
using System;
using UnityEngine;
using Zenject;

public class GoodbyeHandler : IDisposable
{
    public void Dispose()
    {
        Debug.Log("Goodbye World!");
    }
}

public class HelloHandler : IInitializable
{
    public void Initialize()
    {
        Debug.Log("Hello world!");
    }
}

public class Greeter : Kernel
{
    public HelloHandler HelloHandler { get; }
    public Greeter(HelloHandler helloHandler)
    {
        HelloHandler = helloHandler;
        Debug.Log("Created Greeter!");
    }
}

public class TestInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        Container.BindInterfacesAndSelfTo<Greeter>()
            .FromSubContainerResolve().ByMethod(InstallGreeter).AsSingle();
        Container.Bind<HelloHandler>()
            .FromResolveGetter<Greeter>(g => g.HelloHandler).AsSingle();
    }

    void InstallGreeter(DiContainer subContainer)
    {
        subContainer.Bind<Greeter>().AsSingle();

        subContainer.BindInterfacesTo<GoodbyeHandler>().AsSingle();
        subContainer.BindInterfacesAndSelfTo<HelloHandler>().AsSingle();
    }
}
```
`FromResolveGetter<ObjectType>`ConstructionMethodは、BindされているObjectTypeを参照して、そこのプロパティーを用いてBindする方法です。
こうすることで同一のSubContainerからBindすることができました。

## ByInstallerとByMethodどっちを使えばいいの？
ByInstallerを使うべきだと公式のドキュメントに書いてありました。
>ByInstaller - Initialize the subcontainer by using a class derived from Installer. This can be a cleaner and less error-prone alternative than ByMethod, especially if you need to inject data into the installer itself. Less error prone because when using ByMethod it is common to accidentally use Container instead of subContainer in your method.

ByMethodを用いるとうっかり`Container.Bind...`と引数で与えられたsubContainerではなく基のContainerにBindしちゃうことがよくあるので、使うならByInstallerにしようねということですね（ByInstllerならContainer以外を書くとコンパイルエラーになる）
実際私もこのミスで1時間くらい溶かしたことがあるので、使うならByInstallerを使うべきだと思います。

## 参考資料
- [Zenject-Construction Method](https://github.com/modesttree/Zenject#construction-methods)
- [Zenject-SubContainer](https://github.com/svermeulen/Extenject/blob/master/Documentation/SubContainers.md)
- [Zenjectを使うときに気を付けていること](https://adarapata.hatenablog.com/entry/unity-advent-calendar-2019)