【Unity】VContainer最近知った内容をまとめた # 1

# はじめに
この記事は[Hamster Output Advent Calendar 2024](https://qiita.com/advent-calendar/2024/game_hamster)の5日目の記事です！

前回の記事がインストールから動かすまでの内容でしたが、今回はゲーム開発で触ったり記事を読んで調べた内容を少しまとめた記事です。

# 参考にしたリンク

https://vcontainer.hadashikick.jp/ja/

https://zenn.dev/qemel/articles/14d247b9945527

https://qiita.com/sakano/items/b91e01f7fc0a946090ac


https://light11.hatenadiary.com/entry/2021/02/01/203252

# この記事の流れ
最初にVContainerの簡単な利用方法をざっくりと書いた後に、使った機能の違いを書いて行きます。

# VContainerの簡単な利用方法


1.LifetimeScopeを継承したクラスを作成する
2.1で作成したクラスに型またはコンポーネントを登録する
3.登録したインスタンスを受け取る設定をする
MonoBehaviourならInject属性をつける...ピュアC#ならコンストラクタの引き数を用意するなどなど。

# 生成したインスタンスの生存期間を設定する
VContainerではクラスを登録する時にIContainerBuilderのRegisterを利用します。この時、Registerの引き数によって生成されたインスタンスの扱い方が変わります。3つの違いを簡単にまとめました。

### ・Singleton
登録した型を生成した後に、そのインスタンスを使い続けます。複数のクラスでインスタンスを利用する時に、同じ物を利用することができます。VContainerが破棄された時に登録先のIDisposableが自動で実行される。
```C#
protected override void Configure(IContainerBuilder builder)
{
    builder.Register<TestPureCSharp>(Lifetime.Singleton);
}
```

### ・Transient
インスタンスが利用される度に都度、新しいインスタンスを生成します。複数クラスで利用する場合、それぞれのクラスが持つインスタンスは別々の物になります。他のLifetimeと違いインスタンスを生成したクラスでIDisposableを実行する必要があります。

```C#
protected override void Configure(IContainerBuilder builder)
{
    builder.Register<TestPureCSharp>(Lifetime.Transient);
}
```

### ・Scoped
Singletonと少し似ていてLifetimeScope内に、既に作成したインスタンスがあればそれを使い続け、なければ新しく生成して使い続けます。VContainerが破棄された時に登録先のIDisposableが自動で実行される。
```C#
protected override void Configure(IContainerBuilder builder)
{
    builder.Register<TestPureCSharp>(Lifetime.Scoped);
}
```

# 自分が使ったことがあるRegisterまとめ
現在自分がゲーム開発で触ったことがある登録方法をまとめました。

### ・Register
恐らく一番使われている登録方法で、ピュアC#の型を指定して登録するだけでインスタンスの生成ができます。内部で=new()をして、良い感じにキャッシュをしているんだとか...。引き数にLifetimeを指定する必要があるので忘れないようにしましょう。
```C#
protected override void Configure(IContainerBuilder builder)
{
    builder.Register<TestPureCSharp>(Lifetime.Singleton);
}
```

また、登録する時に登録したい型と、取得先を指定して登録することができます。登録される型は最初に、指定した型になりますのでご注意を。
```C#
protected override void Configure(IContainerBuilder builder)
{
    builder.Register<ITestPureCSharp, TestPureCSharp>(Lifetime.Scoped);
}
```
指定した型と、取得先をどちらも登録したい場合、**AsSelf**を利用すると両方とも登録できます。この部分に関しては後でまとめます。
```C#
protected override void Configure(IContainerBuilder builder)
{
    builder.Register<ITestPureCSharp, TestPureCSharp>(Lifetime.Scoped).AsSelf();
}
```

### ・RegisterEntryPoint
登録したピュアC#がUnityのライフサイクルと同様に動作するようになります。複数設定も可能です。正し、VContainaerのインタフェースが1つでも実装されていなければ動作しません。また、Registerと違いインスタンスが登録されないことも注意が必要ですが、**AsSelf**を利用すればインスタンスの登録ができます。

```C#
protected override void Configure(IContainerBuilder builder)
{
    builder.RegisterEntryPoint<TestPureCSharp>(Lifetime.Scoped);
}
```

### ・RegisterComponent
MonoBehaviourを持っているクラスを登録します。引き数に登録するインスタンスが必要なので、事前にクラスを取得する必要があります。Registerと違い、Lifetimeは指定できませんが、Singletonとして登録されるみたいです。
```C#
[SerializeField] private TestMonoBehaviour _testMonoBehaviour;

protected override void Configure(IContainerBuilder builder)
{
    builder.RegisterComponent(_testMonoBehaviour);
}
```

### ・RegisterComponentInHierarchy
LifetimeScopeを配置したシーン上から指定した型を探し登録します。こちらは型のみで登録できますが、シーン上指定した型が無ければエラーになります。
```C#
protected override void Configure(IContainerBuilder builder)
{
    builder.RegisterComponentInHierarchy<TestMonoBehaviour>();
}
```

## 使ってみたけど使いどころが分からないシリーズ

### ・RegisterInstance
既に存在するインスタンスを登録する時に利用します。後から登録する以外に正直、使いどころが分からない....
```C#
protected override void Configure(IContainerBuilder builder)
{
    TestPureCSharp testPureCSharp = new();
    builder.RegisterInstance(testPureCSharp);
}
```

### ・RegisterEntryPointExceptionHandler
EntryPointで発生した例外に対して処理を追加できます。例外処理をあまり使ったことが無いので、利用タイミングがイマイチ分かりませんでした。

```C#
protected override void Configure(IContainerBuilder builder)
{
    builder.RegisterEntryPointExceptionHandler(exception =>
    {
        Debug.LogException(exception);
    });
}
```

### ・RegisterBuildCallback
VContainerのインスタンス登録、受け渡しが終わった時のコールバックです。ラムダ式も使えます。ただ、自分には使いどころが分かりませんでした。そして...ラムダ式があまり分かってないです。

```C#
protected override void Configure(IContainerBuilder builder)
{
    builder.RegisterBuildCallback(container => Debug.Log("登録と依存解決が終わった"));

    builder.RegisterBuildCallback(container =>
    {
        Debug.Log("登録と依存解決が終わった");
    });
}
```

### ・RegisterDisposeCallback
VContainerが破棄された時のコールバックです。こちらもラムダ式が使えます。これも上のコールバックと同様の理由です。

```C#
protected override void Configure(IContainerBuilder builder)
{
    builder.RegisterDisposeCallback(callback => Debug.Log("Dispose"));

    builder.RegisterDisposeCallback(callback =>
    {
        Debug.Log("Dispose");
    });
}
```

# まとめ
今回の書いた内容や調べて気づいたことをまとめると

・VContainerに、登録したクラスに**IDisposable**が実装されている場合、Vcontainerが破棄されたタイミングで自動的に呼び出してくれる。正し、登録した時のLifetimeが**Transient**の場合自分で実行する必要がある。

・VContainerに同じインスタンスを登録することは推奨していない。登録するとエラーになる。

・VContainerのクラスの登録方法は沢山あるが**Register**や**RegisterComponent**辺りを主に利用する。GetComponentみたいな登録ができる**RegisterComponentInHierarchy**もおすすめ。

・**RegisterEntryPoint**を利用するとピュアC#がUnityライフサイクルと同様に動作するようになる。正し、VContainerのインタフェースを1つでも実装されていないと動作はしない。

・一部のRegister関連の処理にはインスタンスを登録することができないが、**AsSelf**を利用すると登録することができる。

・MonoBehaviourでインスタンスを受け取る時は **[Inject]属性をつける**必要があるが、ピュアC#の場合**コンストラクタの引き数から受け取る**必要がある。