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

.NET多态依赖注入:无需手动注册父类型的配置方案

多态依赖注入的自动注册解决方案

问题描述

定义IPlugin接口,以及实现该接口(同时还实现其他接口)的ConcretePlugin类;另外有两个依赖注入类:

  • TestConcrete:构造函数直接依赖ConcretePlugin实例
  • TestList:构造函数依赖IEnumerable<IPlugin>

在使用Microsoft.Extensions.DependencyInjection和Simple Injector测试时,遇到两难问题:

  • 仅注册ConcretePlugin为单例:TestList的构造函数会得到空的IEnumerable<IPlugin>,框架未将ConcretePlugin关联到IPlugin类型
  • 把实例注册为IPlugin类型:TestConcrete无法被实例化,框架找不到ConcretePlugin类型的注册

手动为ConcretePlugin实现的每一个父类型(包括所有接口)单独注册实例过于繁琐——每次新增接口实现都得更新DI注册代码,完全不符合开闭原则。而纯DI场景下,根本不需要显式声明实例的父类型,就能自动完成多态注入,这让DI框架的原生支持显得很鸡肋。

期望DI框架能通过懒加载检查已注册类型是否是请求类型的子类型,自动完成注入,而非要求开发者手动注册所有父类型。至于循环引用(比如TestList实现IPlugin)这类本身就不合理的场景,完全不需要框架考虑。

针对Microsoft.Extensions.DependencyInjection的解决方案

编写扩展方法,自动扫描目标类型的所有接口,将同一个实例注册到所有父类型:

public static IServiceCollection RegisterWithAllInterfaces<T>(this IServiceCollection services, ServiceLifetime lifetime)
    where T : class
{
    var type = typeof(T);
    // 获取类型实现的所有接口
    var interfaces = type.GetInterfaces();
    
    // 先注册自身类型
    services.Add(new ServiceDescriptor(type, type, lifetime));
    
    // 为每个接口注册,指向同一个实例
    foreach (var iface in interfaces)
    {
        services.Add(new ServiceDescriptor(iface, sp => sp.GetRequiredService(type), lifetime));
    }
    
    return services;
}

使用方式:

services.RegisterWithAllInterfaces<ConcretePlugin>(ServiceLifetime.Singleton);

注册后,无论是请求ConcretePlugin还是IPlugin(或其他实现的接口),都会拿到同一个单例实例;TestList的IEnumerable<IPlugin>也会自动包含该实例。

针对Simple Injector的解决方案

Simple Injector可通过自定义注册逻辑实现类似效果:

public static void RegisterWithAllInterfaces<T>(this Container container, Lifestyle lifestyle)
    where T : class
{
    var type = typeof(T);
    var instanceProducer = container.Register(type, type, lifestyle);
    
    foreach (var iface in type.GetInterfaces())
    {
        container.Register(iface, () => instanceProducer.GetInstance(), lifestyle);
    }
}

使用方式:

container.RegisterWithAllInterfaces<ConcretePlugin>(Lifestyle.Singleton);

原理说明

这类扩展方法的核心逻辑:

  1. 先注册目标类型本身
  2. 通过反射获取目标类型实现的所有接口(可扩展支持基类)
  3. 将每个父类型注册为指向目标类型的实例,确保所有请求都拿到同一个实例

这种方式既满足多态注入需求,又避免手动维护所有父类型的注册代码,符合开闭原则。

内容的提问来源于stack exchange,提问作者Stelios Adamantidis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 17:20:18