VContainerでよく言及されている方法では複数のインスタンスを登録できなかった話と、その解決方法を記します。
問題
VContainerでインスタンスを複数個コンテナに登録したい場面があると思います。
その場合、IContainer.RegisterInstance()
メソッドを使って以下のように登録する方法が思い浮かびます。
using VContainer;
using VContainer.Unity;
public class InstanceRegisterLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
builder.RegisterInstance(new MultiRegisterSample());
builder.RegisterInstance(new MultiRegisterSample());
builder.RegisterInstance(new MultiRegisterSample());
}
}
public class MultiRegisterSample {}
シーンに既に複数存在しているコンポーネントを登録したい場合には、IContainerBuilder.RegisterComponent()
メソッドを使ってこんな感じでしょうか。
using UnityEngine;
using VContainer;
using VContainer.Unity;
public class ComponentRegisterLifetimeScope : LifetimeScope
{
[SerializeField] private MultiRegisterComponent multiRegisterComponent1;
[SerializeField] private MultiRegisterComponent multiRegisterComponent2;
[SerializeField] private MultiRegisterComponent multiRegisterComponent3;
protected override void Configure(IContainerBuilder builder)
{
builder.RegisterComponent(multiRegisterComponent1);
builder.RegisterComponent(multiRegisterComponent2);
builder.RegisterComponent(multiRegisterComponent3);
}
}
using UnityEngine;
public class MultiRegisterComponent : MonoBehaviour {}
このような方法は、筆者がいつも参考にさせていただいているHaruma-Kさんのこちらのブログ記事や、
VContainerのリポジトリに2021年立てられたイシューに対するVContainerのコントリビュータの方の回答にも存在し、
複数のインスタンスを登録したい場合には標準的な方法と考えられます。
しかしこの方法をとると、少なくとも2022/12/11現在の最新バージョン1.12.0
では以下のような例外が発生して登録できません。
VContainerException: Conflict implementation type : Registration MultiRegisterSample ContractTypes=[MultiRegisterSample] Singleton VContainer.Internal.ExistingInstanceProvider
VContainer.Internal.CollectionInstanceProvider.Add (VContainer.Registration registration) (at Library/PackageCache/jp.hadashikick.vcontainer@9fff851582/Runtime/Internal/InstanceProviders/CollectionInstanceProvider.cs:49)
VContainer.Internal.Registry.AddToBuildBuffer (System.Collections.Generic.IDictionary`2[TKey,TValue] buf, System.Type service, VContainer.Registration registration) (at Library/PackageCache/jp.hadashikick.vcontainer@9fff851582/Runtime/Registry.cs:70)
VContainer.Internal.Registry.Build (VContainer.Registration[] registrations) (at Library/PackageCache/jp.hadashikick.vcontainer@9fff851582/Runtime/Registry.cs:28)
VContainer.ContainerBuilder.BuildRegistry () (at Library/PackageCache/jp.hadashikick.vcontainer@9fff851582/Runtime/ContainerBuilder.cs:143)
VContainer.ContainerBuilder.Build () (at Library/PackageCache/jp.hadashikick.vcontainer@9fff851582/Runtime/ContainerBuilder.cs:108)
VContainer.Unity.LifetimeScope.Build () (at Library/PackageCache/jp.hadashikick.vcontainer@9fff851582/Runtime/Unity/LifetimeScope.cs:186)
VContainer.Unity.LifetimeScope.Awake () (at Library/PackageCache/jp.hadashikick.vcontainer@9fff851582/Runtime/Unity/LifetimeScope.cs:121)
この例外は、登録するために内部的に用いられているクラスInstanceRegistrationBuilder
が登録を要求されたインスタンスの生存期間としてSingleton
を指定しており、Singleton
なインスタンスは同一スコープに1個しか存在を許されないために発生しているようです。
using System.Runtime.CompilerServices;
namespace VContainer.Internal
{
sealed class InstanceRegistrationBuilder : RegistrationBuilder
{
readonly object implementationInstance;
// 強制的にシングルトンが指定されている
public InstanceRegistrationBuilder(object implementationInstance)
: base(implementationInstance.GetType(), Lifetime.Singleton)
{
this.implementationInstance = implementationInstance;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override Registration Build()
{
var spawner = new ExistingInstanceProvider(implementationInstance);
return new Registration(ImplementationType, Lifetime, InterfaceTypes, spawner);
}
}
}
実際に生存期間をSingleton
以外(ここではScoped
)に変更すると、RegisterInstance()
を用いた場合に限っては例外が発生しなくなりました。
// Scopedに変更
public InstanceRegistrationBuilder(object implementationInstance)
: base(implementationInstance.GetType(), Lifetime.Scoped)
{
this.implementationInstance = implementationInstance;
}
しかし、この記述を変更すると今度は他のシチュエーションで問題が発生することも当然考えられますし、パッケージ側の実装を書き換えるのはできれば避けたいところです。
一方で、RegisterInstance()
やRegisterComponent()
メソッドには生存期間を指定する方法が用意されていません。
では複数インスタンスを登録したい場合はどうすれば良いのでしょうか。
解決方法
VContainerにはRegister()
メソッドが用意されていますが、そのオーバーロードの中にインスタンスを生成するためのデリゲートと生存期間を渡せるものがあるようです。
public static RegistrationBuilder Register<TInterface>(
this IContainerBuilder builder,
Func<IObjectResolver, TInterface> implementationConfiguration,
Lifetime lifetime)
where TInterface : class
=> builder.Register(new FuncRegistrationBuilder(implementationConfiguration, typeof(TInterface), lifetime));
RegisterInstance()
やRegisterComponent()
を用いている部分をRegister()
メソッドを使って書き換えてみます。
using VContainer;
using VContainer.Unity;
public class InstanceRegisterLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
builder.Register(_ => new MultiRegisterSample(), Lifetime.Scoped);
builder.Register(_ => new MultiRegisterSample(), Lifetime.Scoped);
builder.Register(_ => new MultiRegisterSample(), Lifetime.Scoped);
}
}
public class MultiRegisterSample {}
using UnityEngine;
using VContainer;
using VContainer.Unity;
public class ComponentRegisterLifetimeScope : LifetimeScope
{
[SerializeField] private MultiRegisterComponent multiRegisterComponent1;
[SerializeField] private MultiRegisterComponent multiRegisterComponent2;
[SerializeField] private MultiRegisterComponent multiRegisterComponent3;
protected override void Configure(IContainerBuilder builder)
{
builder.Register(_ => multiRegisterComponent1, Lifetime.Scoped);
builder.Register(_ => multiRegisterComponent2, Lifetime.Scoped);
builder.Register(_ => multiRegisterComponent3, Lifetime.Scoped);
}
}
生存期間をSingleton
以外に設定できたことで、どちらも例外を発生させずに動くようになりました。