.NET Core依赖注入:运行时循环场景下正确解析服务的方案选择
方案优先级排序及原因
优先选择方案2,特殊场景可选方案1,完全舍弃方案3
- 方案3直接排除:
手动new ClassA会完全丧失DI容器的依赖管理能力,后续ClassA新增依赖时所有创建点都要同步修改,维护成本极高。如果ClassA有ILogger这类容器托管的依赖,你还需要手动传递,等于完全背离了DI的设计初衷,只有当ClassA无任何外部依赖的极端场景下才可能考虑,否则完全不推荐。 - 方案1的问题说明:
直接在业务代码中使用IServiceScopeFactory+ 服务定位器的写法,会导致你的业务代码和DI容器强耦合,且类的依赖关系不透明:从构造函数只能看到ClassA依赖IServiceScopeFactory,无法得知它实际还依赖IClassA,依赖错误只能在运行期暴露,单元测试时还需要模拟整套DI容器相关的接口,测试成本很高。
微软官方文档提到该用法是作为底层能力说明,并非推荐在业务代码中直接使用,这也是它被很多人视为反模式的核心原因。 - 方案2的优势:
工厂封装的方式把DI相关的逻辑完全隔离在工厂实现类中,业务代码只依赖抽象的IClassAFactory,依赖关系清晰可见,单元测试时只需要模拟工厂返回对应实例即可,完全不需要耦合DI容器。后续如果IClassA的实现、生命周期有调整,只需要修改工厂内部逻辑,不需要改动业务代码,符合开闭原则。额外编写工厂接口和实现的代码量很低,换来的可维护性收益远大于成本。
更优的简化方案
如果你的项目运行在.NET 5及以上版本,微软原生DI已经支持Func<T>类型的自动注入,不需要你手动编写工厂类:
- 服务注册时保持原有配置即可,如果需要每次返回新实例可以注册为
AddTransient,需要Scope生命周期则保持AddScoped
services.AddTransient<IClassA, ClassA>();
- 在ClassA的构造函数中直接注入
Func<IClassA>类型的工厂委托:
class ClassA : IClassA { private readonly Func<IClassA> _classAFactory; public ClassA(ILogger<ClassA> logger, Func<IClassA> classAFactory) { _classAFactory = classAFactory; } public void Foo() { while(true) { var classA = _classAFactory(); // 业务逻辑 } } }
这种方式不需要手动编写任何工厂类和接口,同时保留了工厂模式的所有优势,是运行时获取服务实例的最优选择。
内容的提问来源于stack exchange,提问作者Kris
相关产品推荐
相关产品推荐

