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

.NET 6依赖注入中同一接口多实现如何选择指定类调用

.NET 6 同接口多实现依赖注入指定方案

你当前将ClassA、ClassB都注册为IDestinationFileNaming的实现时,.NET 内置DI容器会按注册顺序存储服务实例,直接注入单个IDestinationFileNaming参数只会拿到最后注册的ClassB。要获取指定实现有以下三种常用方案,你提到的where T : class泛型约束本身仅用于限制泛型参数为引用类型,不具备服务选择能力,但配合泛型接口设计可以实现精准注入,具体见方案3。

方案1:注入全量实现后按类型筛选

这是内置DI原生支持的最简单方案,不需要修改现有注册逻辑,直接在构造函数中注入该接口的所有实现,再通过类型筛选出目标实例即可。
修改ClassC代码如下:

public class ClassC{
    private readonly IDestinationFileNaming _destinationFileNaming;
   
    public ClassC(IEnumerable<IDestinationFileNaming> allNamingImpls)         
    {
        // 从所有注册实现中筛选ClassA的实例
        _destinationFileNaming = allNamingImpls.OfType<ClassA>().FirstOrDefault()
            ?? throw new InvalidOperationException("未找到IDestinationFileNaming的ClassA实现");
    }

    public void Run(){
        // 直接调用即为ClassA的Generate逻辑
        var fileName = _destinationFileNaming.Generate();
    }
} 
  • 优点:无需修改注册逻辑,代码改动量极小
  • 缺点:需要直接依赖具体实现类ClassA,耦合度较高,实现数量较多时筛选有额外性能开销

方案2:封装工厂类做服务路由

如果要降低对具体实现的耦合,或者后续需要动态根据业务条件选择不同实现,可以新增工厂类封装选择逻辑。

  1. 先定义工厂接口与实现:
public interface INamingServiceFactory
{
    IDestinationFileNaming GetImplementation(string implKey);
}

public class NamingServiceFactory : INamingServiceFactory
{
    private readonly IServiceProvider _serviceProvider;
    // 维护key与实现类型的映射关系
    private readonly Dictionary<string, Type> _implMapping = new()
    {
        {"A", typeof(ClassA)},
        {"B", typeof(ClassB)}
    };

    public NamingServiceFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IDestinationFileNaming GetImplementation(string implKey)
    {
        if(!_implMapping.TryGetValue(implKey, out var implType))
            throw new ArgumentException($"不支持的实现key:{implKey}");
        
        return (IDestinationFileNaming)_serviceProvider.GetRequiredService(implType);
    }
}
  1. 注册服务时新增工厂注册:
services.AddScoped<INamingServiceFactory, NamingServiceFactory>();
services.AddScoped<IDestinationFileNaming, ClassA>();
services.AddScoped<IDestinationFileNaming, ClassB>();
  1. 在ClassC中注入工厂获取目标实现:
public class ClassC{
    private readonly IDestinationFileNaming _destinationFileNaming;
   
    public ClassC(INamingServiceFactory namingFactory)         
    {
        // 传入ClassA对应的key即可获取实例
        _destinationFileNaming = namingFactory.GetImplementation("A");
    }

    public void Run(){
        var fileName = _destinationFileNaming.Generate();
    }
} 
  • 优点:业务层不依赖具体实现类,后续新增实现只需要修改工厂映射,符合开闭原则
  • 缺点:需要额外编写工厂类代码,多一层间接调用

方案3:用泛型接口区分不同实现

这个方案就是泛型相关的实现思路:泛型接口不同的类型参数会被DI容器识别为完全独立的服务类型,不需要运行时筛选就能精准注入目标实现。

  1. 改造接口,新增泛型版本(可以保留原有非泛型接口兼容旧逻辑):
// 原有非泛型接口保留
public interface IDestinationFileNaming
{
    string Generate();
}

// 新增泛型接口,where T : class仅限制T为引用类型,不做服务选择
public interface IDestinationFileNaming<T> : IDestinationFileNaming where T : class
{
}
  1. 让实现类继承对应泛型接口,原有业务逻辑不需要改动:
public class ClassA : BaseFileNaming, IDestinationFileNaming<ClassA>
{
    public ClassA() : base() { }
 
    public override string Generate()
    {
        string fileName = string.Empty;
        try
        {
            fileName = BaseName + "AIM_UBW";
        }
        catch (Exception ex)
        { }
        return fileName;
    }
}

public class ClassB : BaseFileNaming, IDestinationFileNaming<ClassB>
{
    public ClassB() : base() { }

    public override string Generate()
    {
        string fileName = string.Empty;
        try
        {
            fileName = BaseName + "DDS_UBW";
        }
        catch (Exception ex)
        { }
        return fileName;
    }
}
  1. 修改服务注册逻辑:
// 注册泛型接口的服务
services.AddScoped<IDestinationFileNaming<ClassA>, ClassA>();
services.AddScoped<IDestinationFileNaming<ClassB>, ClassB>();
// 如果需要兼容获取所有非泛型实现,可以额外注册非泛型服务
services.AddScoped<IDestinationFileNaming>(sp => sp.GetRequiredService<IDestinationFileNaming<ClassA>>());
services.AddScoped<IDestinationFileNaming>(sp => sp.GetRequiredService<IDestinationFileNaming<ClassB>>());
  1. 在ClassC中直接注入对应泛型服务,不需要额外筛选:
public class ClassC{
    private readonly IDestinationFileNaming _destinationFileNaming;
   
    public ClassC(IDestinationFileNaming<ClassA> classANaming)         
    {
        _destinationFileNaming = classANaming;
    }

    public void Run(){
        // 这里拿到的就是确定的ClassA实例
        var fileName = _destinationFileNaming.Generate();
    }
} 
  • 优点:编译期就能确定注入的服务类型,没有运行时筛选开销,出错概率低
  • 缺点:需要修改接口定义和服务注册代码

注意:.NET 7及以上版本新增了键控服务(Keyed Services)能力,可以直接通过AddKeyedScoped给服务打key,注入时用[FromKeyedServices("keyA")]特性就能直接获取对应实现,不需要自己写工厂或者泛型接口,但.NET 6不支持该特性,只能使用上述三种方案。

内容的提问来源于stack exchange,提问作者K.Z

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 03:39:36