MRが楽しい

MRやVRについて学習したことを書き残す

VContainerのHelloWorldを試す その3(クラスを自身のクラスとインタフェースの両方で登録する)

本日は VContainer の小ネタ枠です。
VContainer でクラスを自身のクラスとインタフェースの両方で登録するケースについて記事に残します。

前回記事

以下の前回記事の続きです。
bluebirdofoz.hatenablog.com

クラスを自身のクラスとインタフェースの両方で登録したいケース

例えば HelloWorldService を以下の通り修正しました。
HelloWorldService はロジックを保持するだけでなく IStatable を使って初回のみ自身の処理を実行するようになりました。
処理を実行するたびにその実行回数を表示するログを出力します。
・HelloWorldService.cs

using VContainer.Unity;

namespace ImplementedSample
{
    public class HelloWorldService : IStartable
    {
        private int count = 0;

        public void Hello()
        {
            count++;
            UnityEngine.Debug.Log($"Hello world : {count}");
        }

        public void Start()
        {
            Hello();
        }
    }
}

HelloWorldService をエントリポイントに登録するため、GameLifetimeScope も RegisterEntryPoint で HelloWorldService を登録するよう変更します。
・GameLifetimeScope.cs

sing UnityEngine;
using VContainer;
using VContainer.Unity;

namespace ImplementedSample
{
    public class GameLifetimeScope : LifetimeScope
    {
        [SerializeField]
        private HelloScreen helloScreen;

        protected override void Configure(IContainerBuilder builder)
        {
            // HelloWorldServiceをVContainerのEntryPointに登録する
            builder.RegisterEntryPoint<HelloWorldService>();
            // GamePresenterをVContainerのEntryPointに登録する
            builder.RegisterEntryPoint<GamePresenter>();
            // HelloScreenコンポーネントをDIコンテナに登録する
            builder.RegisterComponent<HelloScreen>(helloScreen);
        }
    }
}

この変更を行ってシーンを再生すると以下のエラーが発生します。

VContainerException: Failed to resolve ImplementedSample.GamePresenter : No such registration of type: ImplementedSample.HelloWorldService

これは Register での登録を RegisterEntryPoint に変更したことで、HelloWorldService がクラスとして登録されなくなったためです。
GamePresenter のコンストラクタで HelloWorldService が解決できなくなっています。

RegisterとRegisterEntryPointで登録する

では改めてクラスとしても登録するため、Register と RegisterEntryPoint の両方で登録するように GameLifetimeScope を変更します。
・GameLifetimeScope.cs

using UnityEngine;
using VContainer;
using VContainer.Unity;

namespace ImplementedSample
{
    public class GameLifetimeScope : LifetimeScope
    {
        [SerializeField]
        private HelloScreen helloScreen;

        protected override void Configure(IContainerBuilder builder)
        {
            // HelloWorldServiceをDIコンテナに登録する
            builder.Register<HelloWorldService>(Lifetime.Singleton);
            // HelloWorldServiceをVContainerのEntryPointに登録する
            builder.RegisterEntryPoint<HelloWorldService>();
            // GamePresenterをVContainerのEntryPointに登録する
            builder.RegisterEntryPoint<GamePresenter>();
            // HelloScreenコンポーネントをDIコンテナに登録する
            builder.RegisterComponent<HelloScreen>(helloScreen);
        }
    }
}

この変更を行ってシーンを再生するとエラーは発生しなくなります。
しかし、以下の通り初回分の実行カウントが正しくインクリメントされていないように見える事象が発生します。

これは Register と RegisterEntryPoint で別々のインスタンスが生成されて別々に参照されてしまうためです。

1つのインスタンスに対して自身のクラスとインタフェースの両方で登録する

1つのインスタンスに対してクラスの登録とエントリポイントの登録をしたい場合、AsImplementedInterfaces と AsSelf を利用します。
以下の通り、GameLifetimeScope を変更しました。
・GameLifetimeScope.cs

using UnityEngine;
using VContainer;
using VContainer.Unity;

namespace ImplementedSample
{
    public class GameLifetimeScope : LifetimeScope
    {
        [SerializeField]
        private HelloScreen helloScreen;

        protected override void Configure(IContainerBuilder builder)
        {
            // HelloWorldServiceをDIコンテナに登録する
            builder.Register<HelloWorldService>(Lifetime.Singleton).AsImplementedInterfaces().AsSelf();
            // GamePresenterをVContainerのEntryPointに登録する
            builder.RegisterEntryPoint<GamePresenter>();
            // HelloScreenコンポーネントをDIコンテナに登録する
            builder.RegisterComponent<HelloScreen>(helloScreen);
        }
    }
}

この変更を行ってシーンを再生すると以下の通り、初回分の実行カウントが正しくインクリメントされるようになりました。

AsImplementedInterfaces

AsImplementedInterfaces は対象のクラスが持つ Interface と関連付けてインスタンスを登録します。
今回 HelloWorldService は IStartable のみインタフェースを実装しているので以下の記述と同義ということになります。

builder.Register<HelloWorldService>(Lifetime.Singleton).AsImplementedInterfaces();
builder.Register<HelloWorldService>(Lifetime.Singleton).As<IStartable>();

AsSelf

AsSelf は対象のクラスと関連付けてインスタンスを登録します。
AsImplementedInterfaces は対象のクラスとの関連付けは行わないので追加で指定する必要があります。