VContainer入門(5) - EntryPoint

# この記事について
[前回](https://qiita.com/sakano/items/7951049b18b8a4a0e3dd)に引き続きVContainerの説明をしていきます。

今回はEntryPointについて説明します。
EntryPointを使うとMonoBehaviourを継承しないクラスでもUpdateなどのイベントを利用できます。

目次ページ: [VContainer入門](https://qiita.com/sakano/items/b91e01f7fc0a946090ac)



# EntryPointを使ってみる

EntryPointとして使うクラスでは用意されたインターフェースを実装します。
次の例では`ITickable`インターフェースを実装したTickableSampleクラスを用意しています。

```cs:TickableSample.cs
public sealed class TickableSample : ITickable
{
    // このメソッドがUpdateのタイミングで呼び出される
    public void Tick()
    {
        Debug.Log("Tick : " + Time.time);
    }
}
```

ITickableはTickメソッドが定義されているだけのインターフェースです。
このクラスを`RegisterEntryPoint`で登録すると使えます。

```cs:LifetimeScope
protected override void Configure(IContainerBuilder builder)
{
    builder.RegisterEntryPoint<TickableSample>();
}
```

これでTickableSampleのTickがUpdateのタイミングで呼び出されるようになります。



# エントリーポイントの登録(RegisterEntryPoint)
`RegisterEntryPoint`で登録されたクラスは、IContainerBuilderの生成直後にインスタンス化されます。
`Register`で登録されたクラスは、他クラスからの依存関係を解決するタイミングでインスタンス化される点で異なります。

つまり、RegisterEntryPointで登録すれば他から依存されていなくても必ずインスタンス化されるエントリーポイントとして利用できます。



## 登録できるインターフェースの一覧

ITickableはUpdateのタイミングで呼び出されますが、Update以外で呼び出されるインターフェースも使えます。
以下のインターフェースが使えます。

|インターフェース|メソッド|呼び出されるタイミング|
|---|---|---|
|IInitializable|Initialize()|クラスが生成された直後|
|IPostInitializable|PostInitialize()|IInitializableが呼び出された直後|
|IStartable|Start()|MonoBehaviourのStartの直前|
|IPostStartable|PostStart()|MonoBehaviourのStartの直後|
|ITickable|Tick()|MonoBehaviourのUpdateの直前|
|IPostTickable|PostTick()|MonoBehaviourのUpdateの直後|
|IFixedTickable|FixedTick()|MonoBehaviourのUpdateの直前|
|IPostFixedTickable|PostFixedTick()|MonoBehaviourのUpdateの直後|
|ILateTickable|LateTick()|MonoBehaviourのLateUpdateの直前|
|IPostLateTickable|PostLateTick()|MonoBehaviourのLateUpdateの直後|

__RegisterEntryPointに渡すクラスは上記のインターフェースを1つ以上実装する必要があります。__
どのインターフェースも実装されていないクラスを渡しても、そのクラスはインスタンス化されません。


また、そのクラスに`IDisposable`が実装されていればIObjectResolverが破棄されるタイミングでDisposeが呼び出されます。



# エントリーポイントの生成

前述の通り、RegisterEntryPointで登録してインスタンス化してもらうにはいずれかのインターフェースが必要です。

インターフェースで提供されるイベントが必要ないクラスでも、VContainerにインスタンス化してほしい時には次の手段があります。

- 実際には使わないIInitializableを実装しておく
- RegisterBuildCallbackを利用する

以下でそれぞれ説明します。

## 使わないIInitializableを実装しておく

こちらは単純に、とりあえずIInitializableを実装しておくだけです。

```cs:EntryPointSample.cs
public sealed class EntryPointSample : IInitializable
{
    public void Initialize()
    {
        // 何もしない
    }
}
```

このクラスをRegisterEntryPointで登録すれば、不要なInitialize呼び出しは発生しますがインスタンスは生成されます。

## RegisterBuildCallbackを利用する

こちらではVContainerのEntryPointの機能に頼らず、RegisterBuildCallbackで強制的にインスタンスを生成してしまいます。

```cs:EntryPointSample.cs
public sealed class EntryPointSample
{
}
```

```cs:LifetimeScope
protected override void Configure(IContainerBuilder builder)
{
    builder.Register<EntryPointSample>(Lifetime.Singleton);

    builder.RegisterBuildCallback(objectResolver =>
    {
        objectResolver.Resolve<EntryPointSample>();
    });
}
```

`RegisterBuildCallback`を使うと、IObjectResolverの生成直後に呼び出されるコールバックを登録できます。
そのコールバックの中でobjectResolver.Resolveを呼び出してインスタンスを生成させています。

こちらの方法を多用する際は、RegisterとRegisterBuildCalbackを行う拡張メソッドを用意しておくと便利です。

```cs:ObjectResolverExtensions.cs
public static class ObjectResolverExtensions
{
    public static RegistrationBuilder RegisterPlainEntryPoint<T>(this IContainerBuilder builder, Lifetime lifetime = Lifetime.Singleton)
    {
        builder.RegisterBuildCallback(objectResolver => objectResolver.Resolve<T>());
        return builder.Register<T>(lifetime);
    }
}
```

この拡張メソッドを用意しておけば、`builder.RegisterPlainEntryPoint<EntryPointSample>();`の一行で先ほどと同じRegisterとRegisterBuildCallbackを呼び出せます。



# 例外処理(RegisterEntryPointExceptionHandler)

`RegisterEntryPointExceptionHandler`で、EntryPoint内で発生した例外を処理できます。

```cs:TickableSample.cs
public sealed class TickableSample : ITickable
{
    public void Tick()
    {
        throw new System.NotImplementedException();
    }
}
```

```cs:LifetimeScope
protected override void Configure(IContainerBuilder builder)
{
    // 例外が発生したときに呼び出されるコールバックを登録できる
    builder.RegisterEntryPointExceptionHandler(e =>
    {
        Debug.LogException(e);
    });

    builder.RegisterEntryPoint<TickableSample>();
}
```

RegisterEntryPointExceptionHandlerを使わない場合、発生した例外はDebug.LogExceptionで出力されます。
なので上記のサンプルコードのRegisterEntryPointExceptionHandlerはデフォルトの動作と同じになります。