You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

无Startup类时如何在C#中实现依赖注入?

嘿,我来帮你搞定这个问题!刚好之前做类似项目时遇到过几乎一样的场景,结合MEF或者基类动态解析的思路都能完美解决你的需求,而且完全不用移除子类的无参构造函数。下面给你详细拆解可行方案:

一、用MEF(Managed Extensibility Framework)实现依赖注入

MEF天生支持属性注入,刚好适配你要保留子类无参构造的需求,不用修改构造函数的逻辑,步骤也很清晰:

1. 标记导出与导入特性

给你的依赖类加上[Export]标记,子类中需要注入的属性加上[Import]标记:

// 定义依赖接口
public interface IDependency
{
    void DoSomething();
}

// 实现依赖类,标记为可导出
[Export(typeof(IDependency))]
public class ConcreteDependency : IDependency
{
    public void DoSomething()
    {
        // 业务逻辑
    }
}

// 子类(保留无参构造)
public class ChildClass : ParentClass
{
    // 标记这个属性需要MEF注入
    [Import]
    public IDependency MyDependency { get; set; }

    public ChildClass()
    {
        // 你的无参构造原有逻辑
    }
}

2. 初始化MEF容器并完成依赖组合

你可以在应用的启动入口(比如控制台的Main方法、WinForms的启动类,或者单元测试的初始化方法)配置MEF容器,加载部件并完成注入:

public static void SetupMef()
{
    // 加载当前程序集里的所有可导出部件
    var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    var container = new CompositionContainer(catalog);

    // 方式1:让MEF直接创建子类实例(自动注入依赖)
    var childInstance = container.GetExportedValue<ChildClass>();

    // 方式2:自己创建子类实例后,让MEF补全依赖
    var childInstance = new ChildClass();
    container.ComposeParts(childInstance);

    // 此时childInstance.MyDependency已经被成功注入
}
二、通过基类+通用DI容器动态解析

如果你不想用MEF,也可以自己基于通用DI容器(比如微软的IServiceCollection、Autofac)在基类里实现动态解析,同样不影响子类的无参构造:

1. 全局配置DI容器

先在应用启动时初始化一个全局DI容器,注册所有需要的依赖:

// 全局DI容器管理类
public static class DIContainer
{
    public static IServiceProvider Provider { get; private set; }

    public static void Initialize()
    {
        var services = new ServiceCollection();
        // 注册依赖(单元测试时可以换成Mock实例)
        services.AddTransient<IDependency, ConcreteDependency>();
        // 注册其他需要的服务
        Provider = services.BuildServiceProvider();
    }
}

2. 基类中添加依赖解析逻辑

在父类里写一个通用的解析方法,用来扫描子类中标记了注入特性的属性,然后从容器中获取实例赋值:

public class ParentClass
{
    // 自定义一个特性,标记需要注入的属性
    [AttributeUsage(AttributeTargets.Property)]
    protected class InjectAttribute : Attribute { }

    // 依赖解析方法,子类可以调用
    protected void ResolveDependencies(object instance)
    {
        // 遍历当前实例所有标记了Inject的属性
        var injectProps = instance.GetType().GetProperties()
            .Where(p => p.GetCustomAttributes(typeof(InjectAttribute), false).Any());

        foreach (var prop in injectProps)
        {
            // 从全局容器获取对应类型的实例
            var service = DIContainer.Provider.GetService(prop.PropertyType);
            if (service != null)
            {
                prop.SetValue(instance, service);
            }
        }
    }
}

// 子类调用解析逻辑
public class ChildClass : ParentClass
{
    [Inject]
    public IDependency MyDependency { get; set; }

    public ChildClass()
    {
        // 在无参构造末尾调用基类的解析方法
        ResolveDependencies(this);
    }
}
三、容器配置的最佳位置

不管用哪种方案,容器的初始化都建议放在应用的启动入口

  • 控制台/控制台测试项目:放在Program.Main方法的最开头;
  • WinForms/WPF:放在主程序的启动类(比如Program.cs)里,在创建主窗体之前执行;
  • 类库项目:可以写一个静态的Initialize方法,由调用方(比如主应用或测试项目)在启动时调用。
单元测试场景适配

针对你提到的单元测试模拟场景,两种方案都能轻松适配:

  • MEF:可以手动创建包含Mock依赖的AssemblyCatalog,或者直接向容器添加模拟导出;
  • 通用DI容器:注册依赖时换成Mock实例(比如用Moq创建的Mock<IDependency>.Object)即可。

内容的提问来源于stack exchange,提问作者Sumit Deshpande

火山引擎 最新活动