如何在Microsoft DI中解析IReadOnlyCollection而非IEnumerable
项目中大量类存在如下依赖:
public class Foo(IReadOnlyCollection<IBar> instances)
使用LightInject进行依赖注入时,该配置默认可正常工作——所有已注册的IBar实现都会在此处被解析。但切换至Microsoft DI后,此配置不再生效,报错信息如下:
System.InvalidOperationException: Unable to resolve service for type 'System.Collections.Generic.IReadOnlyCollection`1[]' while attempting to activate...
请问是否有无需在ServiceCollection中直接注册每个IBar实现,即可解析IReadOnlyCollection<T>的方案?
更新:曾找到一种基于匿名泛型注册IReadOnlyCollection<>的方案,但该方案依赖键注册实现,要求每个IBar注册时都需指定键,操作较为繁琐。
可以通过扩展IServiceCollection,为所有泛型类型IReadOnlyCollection<T>添加自动注册逻辑,利用Microsoft DI原生支持的IEnumerable<T>解析能力来实现需求,无需逐个注册IBar实现或依赖键注册。
实现步骤
- 添加扩展方法,注册
IReadOnlyCollection<T>与ReadOnlyCollection<T>的映射,让DI容器在解析前者时自动转换已注册的IEnumerable<T>实例:
public static class ServiceCollectionExtensions { public static IServiceCollection AddReadOnlyCollectionSupport(this IServiceCollection services) { // 注册泛型服务:将IReadOnlyCollection<T>绑定到ReadOnlyCollection<T> // 容器会自动解析对应的IEnumerable<T>作为构造参数 services.Add(typeof(IReadOnlyCollection<>), typeof(ReadOnlyCollection<>), ServiceLifetime.Transient); return services; } }
- 在DI配置中启用该支持(例如
Program.cs):
var builder = WebApplication.CreateBuilder(args); // 注册你的IBar实现(常规方式,无需额外键) builder.Services.AddTransient<IBar, BarA>(); builder.Services.AddTransient<IBar, BarB>(); // 启用ReadOnlyCollection自动解析支持 builder.Services.AddReadOnlyCollectionSupport();
原理说明
Microsoft DI原生会将所有注册的T实例收集为IEnumerable<T>,而ReadOnlyCollection<T>的构造函数恰好接受IEnumerable<T>参数。通过上述泛型注册,容器在遇到IReadOnlyCollection<IBar>依赖时,会自动解析IEnumerable<IBar>并实例化ReadOnlyCollection<IBar>注入,完全适配原有构造函数的依赖需求。
生命周期适配
如果你的IBar实现使用了Singleton或Scoped生命周期,只需调整扩展方法中的生命周期参数,保持与IEnumerable<T>的生命周期一致即可:
// 例如适配Singleton生命周期 services.Add(typeof(IReadOnlyCollection<>), typeof(ReadOnlyCollection<>), ServiceLifetime.Singleton);
内容的提问来源于stack exchange,提问作者Pallaris

