C# 容器实例生命周期
在 C#(特别是结合 .NET 的依赖注入容器,如 ASP.NET Core 内置的 DI 容器)中,服务(或组件)的生命周期主要有三种:Transient(瞬态)、Scoped(作用域) 和 Singleton(单例)。这些生命周期决定了服务实例何时被创建、重用以及销毁。
以下是它们的区别和适用场景:
1. Transient(瞬态)
- 每次请求都创建一个新实例。
- 适用于轻量级、无状态的服务。
- 优点:隔离性好,不会因状态共享引发问题。
- 缺点:频繁创建/销毁可能影响性能(尤其对重量级对象)。
services.AddTransient<IMyService, MyService>();
每次从 DI 容器解析
IMyService时,都会返回一个全新的MyService实例。
2. Scoped(作用域)
- 在同一个作用域内共享同一个实例,不同作用域使用不同实例。
- 在 ASP.NET Core 中,一个 HTTP 请求就是一个作用域。
- 适用于需要在一次请求中共享状态的服务(如数据库上下文
DbContext)。
services.AddScoped<IMyService, MyService>();
在同一个 HTTP 请求中多次解析
IMyService,会得到同一个实例;但在不同请求中会得到不同实例。
⚠️ 注意:在非 Web 应用(如控制台程序)中,需要手动创建作用域(通过 IServiceScopeFactory)才能体现 Scoped 行为。
3. Singleton(单例)
- 整个应用程序生命周期内只创建一个实例,所有请求共享该实例。
- 适用于全局共享、无状态或线程安全的服务。
- 优点:节省资源,避免重复初始化。
- 风险:如果服务持有可变状态,需确保线程安全。
services.AddSingleton<IMyService, MyService>();
整个应用运行期间,无论多少次解析
IMyService,都返回同一个实例。
对比总结
| 生命周期 | 创建时机 | 实例数量 | 适用场景 |
|---|---|---|---|
| Transient | 每次请求时 | 每次都新实例 | 轻量、无状态、临时对象 |
| Scoped | 每个作用域首次请求时 | 每个作用域一个实例 | Web 请求内共享(如 DbContext) |
| Singleton | 首次请求时(或启动时) | 全局唯一实例 | 全局配置、缓存、日志等 |
补充说明
- 不能从短生命周期服务注入长生命周期服务(例如:Singleton 服务中注入 Scoped 服务是危险的,因为 Scoped 服务可能依赖于请求上下文,而 Singleton 存活时间更长,会导致“捕获作用域外服务”的异常)。
- 反之,长生命周期可以安全地注入短生命周期服务(但通常不推荐,因为短生命周期服务可能无法按预期工作)。
示例(ASP.NET Core)
// Program.cs 或 Startup.cs
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
配合一个测试控制器,你可以观察到 GUID(代表实例 ID)的变化情况,验证不同生命周期的行为。
如有使用第三方容器(如 Autofac、Unity 等),概念类似,但 API 可能略有不同。不过 ASP.NET Core 默认 DI 容器已能满足大多数场景。
