如何用Microsoft.Extensions.DependencyInjection注入泛型接口的IEnumerable?
问题
核心目标:避免业务逻辑中进行类型转换或反射操作,仅允许在依赖注入(DI)注册阶段使用类型转换/反射。
需求:通过Microsoft.Extensions.DependencyInjection注入泛型接口的IEnumerable集合。
当前问题:无论使用协变还是逆变,ConcreteClassFactory中注入的实现实例数量始终为0,寻求可行解决办法。
原代码示例:
public interface IGenericInterface<T> { bool CheckType(Animal obj); void Do(T obj); } public abstract class Animal { } public class Dog : Animal { } public class Cat : Animal { } public class ConcreteClassA : IGenericInterface<Dog> { bool CheckType(Animal obj) { return obj is Dog; // 无状态存储 } public void Do(Dog obj) { // 映射数据并返回(避免类型转换) } } public class ConcreteClassB : IGenericInterface<Cat> { bool CheckType(Animal obj) { return obj is Cat; // 无状态存储 } public void Do(Cat obj) { // 映射数据并返回(避免类型转换) } } public class ConcreteClassFactory { private readonly IEnumerable<IGenericInterface<Animal>> _concretes; public ConcreteClassFactory(IEnumerable<IGenericInterface<Animal>> concretes) { _concretes = concretes; } public void Map(Animal animal) { var concrete = _concretes.FirstOrDefault(x => x.CheckType(animal)); var mapedData = concrete.Do(animal); // 返回映射后的数据 } }
服务注册代码:
builder.Services .AddSingleton<IGenericInterface<Cat>, ConcreteClassB>() .AddSingleton<IGenericInterface<Dog>, ConcreteClassA>() .AddSingleton<ConcreteClassFactory>();
解决方案
问题根源
IGenericInterface<Dog>和IGenericInterface<Animal>是完全独立的封闭泛型类型,DI容器不会自动将它们归为同一集合。同时,原接口的Do(T obj)方法包含输入参数,导致协变/逆变无法生效:
- 协变(
out T)不允许接口包含以T为输入参数的方法; - 逆变(
in T)仅允许将父类型接口实例赋值给子类型接口变量,无法反向兼容,因此IGenericInterface<Dog>不能被当作IGenericInterface<Animal>使用。
可行方案:引入非泛型基接口
通过新增非泛型基接口,将类型转换逻辑限制在接口实现层,业务逻辑中无需处理类型转换,完全符合你的核心目标。
1. 修改接口定义
新增非泛型基接口,泛型接口继承该基接口并调整方法签名:
// 非泛型基接口,统一对外调用契约 public interface IGenericInterface { bool CheckType(Animal obj); void Do(Animal obj); } // 泛型接口继承非泛型接口,保留强类型Do方法 public interface IGenericInterface<T> : IGenericInterface where T : Animal { new void Do(T obj); }
2. 调整实现类
显式实现非泛型接口的Do方法,仅在此处进行安全类型转换(因CheckType已验证类型合法性),泛型Do方法保持强类型无转换:
public class ConcreteClassA : IGenericInterface<Dog> { public bool CheckType(Animal obj) { return obj is Dog; } // 强类型泛型Do方法,无需转换 public void Do(Dog obj) { // 处理Dog的业务逻辑 } // 显式实现非泛型接口Do方法,仅此处做类型转换 void IGenericInterface.Do(Animal obj) { Do((Dog)obj); } } public class ConcreteClassB : IGenericInterface<Cat> { public bool CheckType(Animal obj) { return obj is Cat; } public void Do(Cat obj) { // 处理Cat的业务逻辑 } void IGenericInterface.Do(Animal obj) { Do((Cat)obj); } }
3. 修改工厂类
注入非泛型接口的IEnumerable集合,业务逻辑中直接调用非泛型方法:
public class ConcreteClassFactory { private readonly IEnumerable<IGenericInterface> _concretes; public ConcreteClassFactory(IEnumerable<IGenericInterface> concretes) { _concretes = concretes; } public void Map(Animal animal) { var concrete = _concretes.FirstOrDefault(x => x.CheckType(animal)); concrete?.Do(animal); // 返回映射后的数据 } }
4. 调整服务注册
将每个泛型实现同时注册为非泛型接口实例,DI容器会自动收集所有非泛型实例注入工厂:
builder.Services // 注册泛型接口实现 .AddSingleton<IGenericInterface<Dog>, ConcreteClassA>() // 将泛型实例注册为非泛型接口 .AddSingleton<IGenericInterface>(sp => sp.GetRequiredService<IGenericInterface<Dog>>()) .AddSingleton<IGenericInterface<Cat>, ConcreteClassB>() .AddSingleton<IGenericInterface>(sp => sp.GetRequiredService<IGenericInterface<Cat>>()) .AddSingleton<ConcreteClassFactory>();
如果实现类较多,可通过反射批量注册简化代码:
var genericInterfaceType = typeof(IGenericInterface<>); var concreteTypes = Assembly.GetExecutingAssembly().GetTypes() .Where(t => !t.IsAbstract && !t.IsInterface && t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericInterfaceType)); foreach (var concreteType in concreteTypes) { var implementedGenericInterface = concreteType.GetInterfaces() .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericInterfaceType); // 注册泛型接口 builder.Services.AddSingleton(implementedGenericInterface, concreteType); // 注册为非泛型接口 builder.Services.AddSingleton(typeof(IGenericInterface), sp => sp.GetRequiredService(implementedGenericInterface)); } builder.Services.AddSingleton<ConcreteClassFactory>();
内容的提问来源于stack exchange,提问作者Max Naumov
相关产品推荐
相关产品推荐

