如何解决IContainerRegistry中同一接口多实例的解析问题?
问题原因
你最初多次调用RegisterSingleton<IPlayer, X>()时,Prism使用的容器(如DryIoc/Unity)会默认覆盖之前的注册,导致最终IEnumerable<IPlayer>只能拿到最后一个注册的实例,这就是代码无法正常工作的核心原因。
最优解决方案
下面提供几种更直观、易维护的方式,替代手动构建集合的“双重注册技巧”:
方案1:使用RegisterManySingleton批量注册(推荐)
Prism提供了RegisterManySingleton方法,它会自动将实现类的**所有接口(包括继承的父接口)**注册为单例。这样既可以通过子接口(如IRobot)解析单个实例,也能通过IPlayer接口的集合拿到所有实现:
public class GameModule: IModule { public void RegisterTypes(IContainerRegistry containerRegistry) { RegisterPlayers(containerRegistry); } private void RegisterPlayers(IContainerRegistry containerRegistry) { // 批量注册所有Player实现类 containerRegistry.RegisterManySingleton( new[] { typeof(Robot), typeof(Human), typeof(Elf), typeof(Vampire), typeof(Saiyan) } ); } }
之后BattleGrid的代码无需修改,注入IEnumerable<IPlayer>就能自动拿到所有实例。
方案2:手动关联子接口与父接口注册
如果需要更精细的控制,可以为每个实现类同时注册子接口和父接口,确保父接口的每个注册都指向对应的单例实例:
public class GameModule: IModule { public void RegisterTypes(IContainerRegistry containerRegistry) { RegisterPlayers(containerRegistry); } private void RegisterPlayers(IContainerRegistry containerRegistry) { RegisterPlayer<IRobot, Robot>(containerRegistry); RegisterPlayer<IHuman, Human>(containerRegistry); RegisterPlayer<IElf, Elf>(containerRegistry); RegisterPlayer<IVampire, Vampire>(containerRegistry); RegisterPlayer<ISaiyan, Saiyan>(containerRegistry); } // 通用注册方法,复用逻辑 private void RegisterPlayer<TSubInterface, TImplementation>(IContainerRegistry containerRegistry) where TSubInterface : class, IPlayer where TImplementation : class, TSubInterface { // 注册子接口的单例 containerRegistry.RegisterSingleton<TSubInterface, TImplementation>(); // 注册父接口,指向子接口的单例实例 containerRegistry.RegisterSingleton<IPlayer>(sp => sp.Resolve<TSubInterface>()); } }
这种方式下,每个IPlayer的注册都是独立的,容器会将所有实例收集到IEnumerable<IPlayer>中,同时保留通过子接口解析单个实例的能力。
方案3:针对DryIoc容器的专属配置
如果你的Prism项目使用默认的DryIoc容器,可以在注册时添加asResolutionCall: true配置,告诉容器将该实现加入到父接口的集合解析中:
public class GameModule: IModule { public void RegisterTypes(IContainerRegistry containerRegistry) { RegisterPlayers(containerRegistry); } private void RegisterPlayers(IContainerRegistry containerRegistry) { containerRegistry.RegisterSingleton<IPlayer, Robot>(setup: DryIoc.Setup.With(asResolutionCall: true)); containerRegistry.RegisterSingleton<IPlayer, Human>(setup: DryIoc.Setup.With(asResolutionCall: true)); containerRegistry.RegisterSingleton<IPlayer, Elf>(setup: DryIoc.Setup.With(asResolutionCall: true)); containerRegistry.RegisterSingleton<IPlayer, Vampire>(setup: DryIoc.Setup.With(asResolutionCall: true)); containerRegistry.RegisterSingleton<IPlayer, Saiyan>(setup: DryIoc.Setup.With(asResolutionCall: true)); } }
这样注册后,容器会保留所有IPlayer的实现,注入IEnumerable<IPlayer>即可获取全部实例。
效果验证
无论使用哪种方案,BattleGrid的代码都不需要修改,直接注入IEnumerable<IPlayer>就能调用所有实例的Initialize()方法:
public class BattleGrid { private readonly IEnumerable<IPlayer> _players; public BattleGrid(IEnumerable<IPlayer> players){ _players = players; } public void InitializeAllPlayers() => _players.ToList().ForEach(player => player.Initialize()); }
(注:原代码中的ForEachDo如果是自定义扩展方法可以保留,否则建议改为ToList().ForEach())
内容的提问来源于stack exchange,提问作者Laendor

