IServiceCollection 是接口不能 new,需用 ServiceCollection() 构建后调用 BuildServiceProvider() 获取 IServiceProvider;注册必须在构建前完成,否则 GetService 抛 NullReferenceException。

为什么 IServiceCollection 不能直接 new 出来用
因为 IServiceCollection 是一个抽象容器接口,不是实现类;你看到的 new ServiceCollection() 是在构建阶段用的临时集合,它本身不提供服务解析能力。真正干活的是 IServiceProvider,而它必须从 BuildServiceProvider() 或更推荐的 Host.CreateDefaultBuilder().Build() 中获取。
常见错误现象:NullReferenceException 在调用 GetService<t>()</t> 时抛出,往往是因为忘了调用 BuildServiceProvider(),或者用了未注册类型的泛型参数。
- 注册必须在
BuildServiceProvider()之前完成,顺序敏感 - 不要在注册过程中就调用
GetService—— 此时容器还没建好 - 生产环境避免频繁调用
BuildServiceProvider(),每次调用都新建一个完整容器,开销大且可能泄露单例实例
AddSingleton、AddScoped、AddTransient 怎么选
三者区别不在“要不要创建”,而在于“谁来管理生命周期”和“什么时候销毁”。选错会导致状态污染、内存泄漏或并发问题。
使用场景举例:数据库上下文(DbContext)必须用 AddScoped,否则多请求共享同一实例会出竞态;日志器(ILogger)通常用 AddSingleton,因为内部已线程安全;DTO 映射器这类无状态工具类,AddTransient 更轻量。
-
AddSingleton:整个应用生命周期只创建一次,所有地方拿到的是同一个实例 -
AddScoped:每个请求(HTTP Context)或作用域(using var scope = provider.CreateScope())内共享一个实例 -
AddTransient:每次GetService都新建,适合无状态、轻量、不可变对象
性能影响:过度使用 Singleton 可能导致静态资源堆积;过度使用 Transient 会增加 GC 压力;Scoped 在非 Web 场景(如控制台)需手动创建作用域,否则退化为 Singleton。
构造函数注入失败?检查这三点
最常见的不是代码写错,而是容器找不到匹配的构造函数——C# DI 默认只认**public 无默认值参数的构造函数**,且所有参数类型必须已注册或可推导。
错误现象:InvalidOperationException: Unable to resolve service for type 'X' while attempting to activate 'Y'
- 确认被注入类型是否已调用
AddXxx<t>()</t>注册,注意泛型闭包是否一致(比如注册了IMyService<string></string>,但构造函数要的是IMyService<int></int>) - 检查构造函数参数是否全是 public,且没有
default值(如MyClass(IOptions<t> opts = null)</t>会跳过这个构造函数) - 避免循环依赖:A 依赖 B,B 又依赖 A —— 容器无法解出,会直接抛
InvalidOperationException,提示 “A depends on B which depends on A”
第三方库怎么接入 .NET 内置 DI 容器
很多库(比如 Microsoft.EntityFrameworkCore、Microsoft.Extensions.Http)自带扩展方法,如 AddDbContext<t>()</t> 或 AddHttpClient(),它们本质就是帮你批量注册了一堆服务,并配置好生命周期。
自己封装的类库也该照此设计:提供 IServiceCollection.AddMyLibrary() 扩展方法,内部完成所有必要注册,而不是让用户一个个手写 AddSingleton。
- 优先用官方扩展方法,它们已处理好兼容性(如 EF Core 的
AddDbContext自动注册IDbContextFactory) - 如果库没提供扩展方法,查文档看它要求哪些服务必须提前注册(比如某些 SDK 要求先注册
IConfiguration或IOptions) - 别把
IServiceProvider直接塞进自己的服务里传下去 —— 这是 Service Locator 反模式,破坏可测试性和显式依赖
容易被忽略的一点:.NET 6+ 默认启用 EnableServiceProviderValidation,会在启动时检查注册冲突(比如同一接口注册了两次不同实现),但仅限开发环境。上线前务必确认验证没报错,否则运行时才暴露就晚了。











