Search code examples
c#dependency-injectioninterfaceprism

How I can resolve multiple instance childs of the same interface registered in IContainerRegistry?


I'm a rookie in Prism and in particular in .NET environment.

This is a brief description around the problem: I have a hierarchy of several interfaces (IRobot, IHuman, IElf, IVampire, ISaiyan) childs of the same parent interface (IPlayer). Each leaf-interface has of course its implementation classes (Robot, Human, Elf, Vampire, Saiyan). The IPlayer has the method void Initialize().

In the GameModule class (child of Prism.Modularity.IModule) there is the registration of all the implementations, to the same parent interface, as singletons:

public class GameModule: IModule
{

    private void RegisterPlayers(IContainerRegistry containerRegistry)
    {
        containerRegistry.RegisterSingleton<IPlayer, Robot>();
        containerRegistry.RegisterSingleton<IPlayer, Human>();
        [...]
    }

}

Later, in the BattleGrid I need to resolve all these implementations to call the same method Initialize() in this way:

public class BattleGrid
{

    private readonly IEnumerable<IPlayer> _players;

    public BattleGrid(IEnumerable<IPlayer> players){
        _players = players;
    }
 
    public void InitializeAllPlayers() => _players .ForEachDo(player=>player.Initialize());
}

Unfortunately this is not working. How can I resolve it?

Right now I used this "double-registration trick" but I don't like it very much: in the GameModule class I register first all the implementations with the related interface, then I register a list of IPlayer by resolving each interface registred:

public class GameModule: IModule
{

    private void RegisterPlayers(IContainerRegistry containerRegistry)
    {
        containerRegistry.RegisterSingleton<IRobot, Robot>();
        containerRegistry.RegisterSingleton<IHuman, Human>();
        [...]
        
        containerRegistry.RegisterSingleton<IEnumerable<IPlayer>>(
            container => new List<IPlayer> 
            {
                container.Resolve<IRobot>(),
                container.Resolve<IHuman>(),
                [...]
            }
        );
    }    
}

I don't like it because another developer will struggle for hours before he/she found this "double registration trick".


Solution

  • Sorry, I misunderstood your question. It seems like you need automatic discovery, for scanning and registering your implementations.

    Scan all loaded assemblies for types that implement IPlayer and register each as a singleton. No sweat.

    Create a container extension method

    public static class ContainerRegistryExtensions
    {
        public static void RegisterAllPlayers<T>(this IContainerRegistry containerRegistry)
        {
            var playerType = typeof(T);
    
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            var playerImplementations = assemblies
                .SelectMany(a => a.GetTypes())
                .Where(t => playerType.IsAssignableFrom(t) && !t.IsAbstract && t.IsClass);
    
            foreach (var implementation in playerImplementations)
            {
                containerRegistry.RegisterSingleton(playerType, implementation);
            }
        }
    }
    

    The extension method scans all loaded assemblies for types that implement IPlayer.

    Use the Extension Method in Your GameModule

    public class GameModule : IModule
    {
        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterAllPlayers<IPlayer>();
        }
    
        public void OnInitialized(IContainerProvider containerProvider)
        {
            // Initialization logic if needed
        }
    }
    

    Now adding new implementations of IPlayer only requires creating the class, no changes needed in the registration logic.