c#高级依赖注入核心是正确使用microsoft.extensions.dependencyinjection的生命周期语义与构造函数约束;关键在注册时机、解析边界、释放契约三点,而非堆砌功能。

直接说结论:C# 中“高级”依赖注入不靠手写容器,而是用 Microsoft.Extensions.DependencyInjection 配合明确的生命周期语义和构造函数约束来落地;所谓“高级”,本质是理解注册时机、解析边界、释放契约这三点,而不是堆功能。
注册服务时必须选对生命周期模式
不是所有类型都适合 AddTransient,也不是所有单例都能随便 AddSingleton。选错会导致内存泄漏、状态污染或并发异常。
-
AddTransient:每次调用GetService<t>()</t>或构造注入时都新建实例。适合无状态、轻量工具类(如GuidGenerator、JsonSerializerOptions) -
AddScoped:在同一个IServiceScope内复用(如 ASP.NET Core 中一次 HTTP 请求),超出作用域即释放。适合需跨多个组件共享上下文但又不能全局存活的对象(如 EF Core 的DbContext) -
AddSingleton:整个应用生命周期内只创建一次。仅适用于真正无状态、线程安全、且不持有任何IDisposable资源的对象;若内部持有了FileStream或数据库连接,必须确保它实现了IDisposable并由容器自动调用Dispose()
构造函数注入是唯一被容器原生支持的注入方式
属性注入和方法注入在 .NET 官方容器中默认不启用,强行开启需要额外配置(如反射遍历 SetProperty),不仅性能差,还破坏了对象不可变性与可测试性。
- 容器只解析 public 构造函数,且要求参数类型全部已在容器中注册(或为基本类型/可推导类型如
ILogger<t></t>) - 若一个类有多个构造函数,容器会选择参数最多、所有参数都可解析的那个——但这是危险行为,应显式删掉冗余构造函数,只保留一个
- 禁止在构造函数里做耗时操作(如连接数据库、读文件),否则会阻塞整个服务启动流程;这类逻辑应移到
IHostedService或懒加载属性中
手动解析服务前必须确认作用域有效性
直接从 IServiceProvider 调用 GetService<t>()</t> 看似方便,但极易踩坑:在 Singleton 类中解析 Scoped 服务会失败,而容器不会报错,只会返回 null(除非启用了 ValidateScopes = true)。
- ASP.NET Core 默认开启作用域验证(
ValidateScopes = true),此时尝试在 Singleton 中解析 Scoped 服务会抛出InvalidOperationException:“Cannot resolve scoped service 'IX' from root provider.” - 若必须跨作用域获取服务(如后台任务中用 DbContext),应通过
IServiceScopeFactory.CreateScope()显式创建新作用域,用完立即Dispose() - 永远不要把
IServiceProvider存在静态字段或长期存活对象中——它本身不是线程安全的,且可能引用已释放的 Scoped 实例
最常被忽略的一点:DI 容器不是万能对象工厂。它只管理你明确注册过的类型,且只负责“创建”和“释放”,不负责“业务逻辑编排”。过度依赖容器自动解析深层依赖链(A→B→C→D→E),会让调试变得极其困难——某处 NullReferenceException 往往是因为中间某个接口漏注册,而非代码写错。










