无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




