You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 04:27:34