Resolution of multiple interface registration
I tried such approach:
I has classes DataA, DataB, DataC, ...
Container registers in transient-way: ControllerA, ControllerB, ControllerC, ... (each controller implements IController and constructor requires only one exact Data* instance, ex: ControllerB(DataB data))
Then I creating sub-scope with only one registered Data* instance; then I instantiate View, which represents any controller, so it injects controller with [Inject] private readonly IController controller;.
I assumed that IController will be resolved as any resolvable type, but it fails for each controller except first registered.
Is this a bug or should I search for another approach?
I'm not 100% sure what you mean, but if you're registering multiple concrete classes to a single interface, they will be implicitly registered as a collection:
https://vcontainer.hadashikick.jp/registering/register-collection
So in your views construction method you would need something like:
[Inject]
private void Construct(IReadOnlyList<IController> registeredControllers)
{
foreach(var controller in registeredControllers)
{
if( controllerB is ControllerB)
_thisController = controllerB;
}
}
I think if you just have IController it will always return the first registered.
I have registered ControllerA : IController, ControllerB : IController, ... and only one Data in same time,
then I try container.CreateScope(b => b.RegisterInstance(concreteData)).Resolve<IController>(),
so I want Resolve to find any resolvable IController, but it tries to create only ControllerA.
From VContainer I want to automate this:
IController controller = concreteData switch
{
DataA dataA => new ControllerA(dataA),
DataB dataB => new ControllerB(dataB),
_ => throw new NotImplementedException()
}
It have a lot of applications:
- Game Rewards described in
ScriptableObjectthat obviously should be separated with Controller logic and in runtime I do not need exact types of reward data, I just docontainer.CreateScope(b => b.RegisterInstance(rewardData)).Resolve<IReward>().Give() - Game Quests works in same way
container.CreateScope(b => b.RegisterInstance(nextQuestData)).Resolve<IQuestFlow>().Start()
I have already solved it, but it would be great if VContainer can act this way out of box.
Workaround (it works only if resolvable type requires single type, but in general I want resolve even ControllerAX(DataA, DataX)):
public static class CoupleFactory<TAbstractSource, TAbstractDestination>
{
private static readonly Dictionary<Type, Type> couples = new();
public static IReadOnlyCollection<Type> Destinations => couples.Values;
public static void Register<TConcreteSource, TConcreteDestination>()
where TConcreteSource : TAbstractSource
where TConcreteDestination : TAbstractDestination
=> couples[typeof(TConcreteSource)] = typeof(TConcreteDestination);
public static TAbstractDestination GetResolver(IObjectResolver container)
{
try
{
var concreteSource = container.Resolve(typeof(TAbstractSource));
var concreteDestination = couples[concreteSource.GetType()];
return (TAbstractDestination)container.Resolve(concreteDestination);
}
catch (Exception e)
{
Debug.LogException(e);
}
return default;
}
}
public static class CoupleFactory
{
public static void RegisterCoupleFactory<TAbstractSource, TAbstractDestination>(this IContainerBuilder builder)
where TAbstractDestination : class
{
foreach (var destination in CoupleFactory<TAbstractSource, TAbstractDestination>.Destinations)
builder.Register(destination, Lifetime.Transient);
builder.Register(
CoupleFactory<TAbstractSource, TAbstractDestination>.GetResolver, Lifetime.Transient);
}
}
and each couple IData and IController should be registered with:
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void Register() => CoupleFactory<IData, IController>.Register<ConcreteData, ConcreteController>();
also container building should contain this:
builder.RegisterCoupleFactory<IData, IController>()