.NET 6+ 内置 IServiceCollection + ServiceProvider 已足够,第三方容器仅在需模块化注册、动态解析或高级生命周期控制时引入;注册需按生命周期选 AddTransient(无状态工具类)、AddScoped(每请求复用,如 DbContext)、AddSingleton(全局单例,须线程安全);非 Web 环境须手动创建并释放作用域;接口与实现注册须匹配泛型类型和约束,且确保程序集已加载。

不用自己写容器,.NET 6+ 内置的 IServiceCollection + ServiceProvider 就够用,第三方容器(如 Autofac、DryIoc)只在需要模块化注册、动态解析或生命周期高级控制时才值得引入。
注册服务时,AddTransient / AddScoped / AddSingleton 到底怎么选
关键看实例生命周期是否跨请求、是否带状态、是否线程安全:
-
AddTransient:每次GetService或构造函数注入都新建实例;适合无状态工具类(如IMapper、IEmailSender),但别用于含缓存或数据库连接的对象 -
AddScoped:每个 HTTP 请求(或using var scope = provider.CreateScope())内复用一个实例;适合 Entity Framework 的DbContext、带请求上下文的仓储类 -
AddSingleton:整个应用生命周期共用一个实例;适合配置读取器(IConfiguration)、日志提供器(ILoggerFactory)、全局计数器;注意:必须线程安全,且不能持有Scoped依赖(否则会引发InvalidOperationException: Cannot resolve scoped service from root provider)
在非托管上下文(如控制台、Worker Service、单元测试)中手动创建作用域
.NET Web 项目自动为每个 HTTP 请求创建 IScopedService 作用域,但控制台程序或后台任务里没有这个机制,直接注入 Scoped 服务会报错:
System.InvalidOperationException: No service for type 'MyApp.IRepository' has been registered.
正确做法是显式创建作用域:
using var scope = provider.CreateScope(); var repo = scope.ServiceProvider.GetRequiredService<irepository>(); repo.DoWork();</irepository>
- 记得
using释放作用域,否则DbContext等资源不会被及时释放 - 不要把
scope.ServiceProvider存起来长期使用——它只在当前作用域内有效 - 单元测试中常用
new ServiceCollection().AddScoped<...>().BuildServiceProvider()搭建轻量环境
接口与实现不匹配、泛型注册失败的常见原因
典型错误现象:GetRequiredService 返回 null 或抛出 InvalidOperationException:
- 忘了注册具体实现:只写了
services.AddScoped,但没指定实现类型,应写成>() services.AddScoped, CreateUserCommandHandler>() - 泛型约束没对齐:比如
CreateUserCommandHandler实现了ICommandHandler,但类声明里漏了where T : ICommand,导致编译通过但运行时无法匹配 - 程序集未加载:使用
Assembly.GetExecutingAssembly()扫描注册时,确保目标类型所在程序集已由宿主加载(.NET 6+ 默认不加载所有引用程序集)
最常被忽略的一点:依赖注入不是魔法——它只解决“谁来创建对象”,不解决“对象内部逻辑是否正确”。注册对了,不代表 DbContext 连得上数据库,也不代表 IHttpClientFactory 配置了正确的 BaseAddress。容器报错只是起点,真正的问题往往藏在被注入对象自己的初始化过程里。










