理解依赖注入的生命周期:Singleton、Scoped 和 Transient
发布: (2025年12月3日 GMT+8 18:55)
6 min read
原文: Dev.to
Source: Dev.to
Singleton
关键特性
- 整个应用程序生命周期只有一个实例
- 在所有请求之间共享
- 第一次请求时创建一次
- 应用程序关闭时释放
何时使用
- 无状态服务(没有实例特定的数据)
- 创建成本高的服务(缓存、HTTP 客户端)
- 共享资源(配置、日志、缓存)
// Register as singleton
services.AddSingleton();
services.AddSingleton();
示例:缓存服务
public class CacheService : ICacheService
{
private readonly Dictionary _cache = new();
private readonly object _lock = new();
public void Set(string key, T value)
{
lock (_lock) { _cache[key] = value; }
}
public T Get(string key)
{
lock (_lock)
{
return _cache.TryGetValue(key, out var value) ? (T)value : default;
}
}
}
注意事项
- 共享可变状态
- 线程安全问题(确保适当的同步)
- 持有非托管资源时实现
IDisposable
// BAD: Singleton with mutable state
public class BadService
{
public string CurrentUserId { get; set; } // Shared across all requests!
}
Scoped
关键特性
- 每个作用域(通常是每个 HTTP 请求)一个实例
- 在同一作用域内共享,不同作用域之间不共享
- 作用域开始时创建
- 作用域结束时释放
何时使用
- 数据库上下文(例如 Entity Framework
DbContext) - 需要在请求期间保持状态的请求特定服务
- 工作单元(Unit‑of‑Work)模式
// Register as scoped
services.AddScoped();
services.AddScoped();
示例:数据库上下文和订单服务
public class ApplicationDbContext : DbContext
{
public DbSet Orders { get; set; }
}
public class OrderService
{
private readonly ApplicationDbContext _context;
public OrderService(ApplicationDbContext context) => _context = context;
public async Task CreateOrder(Order order)
{
_context.Orders.Add(order);
await _context.SaveChangesAsync();
return order;
}
}
注意事项
- 将 scoped 服务注入到 singleton 中(会导致生命周期不匹配)
// BAD: Singleton depending on scoped service
public class BadSingleton
{
private readonly IScopedService _scoped; // Error!
}
// GOOD: Resolve scoped service via IServiceProvider
public class GoodSingleton
{
private readonly IServiceProvider _serviceProvider;
public GoodSingleton(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;
public void DoWork()
{
using var scope = _serviceProvider.CreateScope();
var scoped = scope.ServiceProvider.GetRequiredService();
scoped.DoWork();
}
}
- 不要在后台任务中捕获 scoped 服务,除非创建了适当的作用域。
Transient
关键特性
- 每次请求都会创建新实例
- 消费者之间不共享
- 按需创建,超出作用域后由垃圾回收释放
- 三种方式中生命周期最短
何时使用
- 轻量级、创建成本低的服务
- 无状态操作
- 必须不共享的服务(共享会导致错误)
// Register as transient
services.AddTransient();
services.AddTransient, OrderValidator>();
示例:订单验证器
public class OrderValidator : IValidator
{
public ValidationResult Validate(Order order)
{
var result = new ValidationResult();
if (order.Items == null || order.Items.Count == 0)
result.AddError("Order must have at least one item");
return result;
}
}
注意事项
- 将成本高的服务注册为 transient 会影响性能
- 将 transient 实例保存在静态字段中会导致内存泄漏
- 当 singleton 已足够时,避免不必要的创建
对比表
| 方面 | Singleton | Scoped | Transient |
|---|---|---|---|
| 实例数量 | 整个应用只有一个 | 每个作用域一个 | 每次请求都会新建 |
| 生命周期 | 应用程序生命周期 | 作用域生命周期(每个请求) | 最短——作用域结束后即被回收 |
| 内存使用 | 低(共享) | 中等 | 高(实例很多) |
| 线程安全 | 必须是线程安全的 | 通常不需要 | 通常不需要 |
| 释放时机 | 应用关闭时 | 作用域结束时 | 垃圾回收时 |
| 典型使用场景 | 无状态、创建成本高的服务 | 请求特定服务 | 轻量级、无状态服务 |
服务注册示例
public void ConfigureServices(IServiceCollection services)
{
// Singleton: Shared across all requests
services.AddSingleton();
// Scoped: One per HTTP request
services.AddScoped();
// Transient: New instance each time
services.AddTransient();
}
后台工作者示例(在托管服务中使用 Scoped 服务)
public class BackgroundWorker : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public BackgroundWorker(IServiceProvider serviceProvider) =>
_serviceProvider = serviceProvider;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Create a scope for each iteration
using var scope = _serviceProvider.CreateScope();
var scopedService = scope.ServiceProvider.GetRequiredService();
await scopedService.DoWorkAsync();
await Task.Delay(1000, stoppingToken);
}
}
}
生命周期依赖规则
- 服务只能依赖 相同或更长 生命周期的服务。
| 服务生命周期 | 允许的依赖 |
|---|---|
| Singleton | Singleton |
| Scoped | Singleton、Scoped |
| Transient | Singleton、Scoped、Transient |
常见错误
// BAD: Singleton depending on scoped service
public class SingletonService
{
private readonly IScopedService _scoped; // Error!
}
// GOOD: Resolve scoped service via IServiceProvider when needed
public class SingletonService
{
private readonly IServiceProvider _serviceProvider;
public SingletonService(IServiceProvider serviceProvider) =>
_serviceProvider = serviceProvider;
public void DoWork()
{
using var scope = _serviceProvider.CreateScope();
var scoped = scope.ServiceProvider.GetRequiredService();
// Use scoped service...
}
}