自学内容网 自学内容网

详细解析 .NET 依赖注入的三种生命周期模式

在这里插入图片描述

一、Transient(瞬时生命周期)

原理
客户端 DI容器 请求IMyService 创建新的MyService实例 返回新实例 再次请求IMyService 再次创建新的MyService实例 返回另一个新实例 客户端 DI容器
使用方式
// 注册服务
builder.Services.AddTransient<IMyService, MyService>();

// 使用示例
public class ClientService
{
    private readonly IMyService _service1;
    private readonly IMyService _service2;

    public ClientService(IMyService service1, IMyService service2)
    {
        // 两个参数会收到不同的实例
        _service1 = service1;
        _service2 = service2;
    }
}
核心特性
  1. 每次请求创建新实例
  2. 不共享状态
  3. 自动释放(当请求处理完成时)
适用场景
  • 轻量级无状态服务(如计算器、验证器)
  • 需要线程隔离的服务
  • 每次操作需要全新状态的场景
// 典型应用:数据转换服务
public interface IDataTransformer
{
    string Transform(string input);
}

public class ReverseTransformer : IDataTransformer
{
    public string Transform(string input) 
        => new string(input.Reverse().ToArray());
}

// 注册
services.AddTransient<IDataTransformer, ReverseTransformer>();
优势
  1. 内存安全:不会意外共享状态
  2. 线程安全:每个线程使用独立实例
  3. 简单可靠:无需考虑状态管理
劣势
  1. 性能开销:频繁创建/销毁对象
  2. 内存碎片:大量短期对象增加GC压力
  3. 资源浪费:不适合初始化成本高的服务

二、Scoped(作用域生命周期)

原理
请求1 DI容器 作用域 请求2 S2 开始请求 创建作用域 请求IUserRepository 创建新实例 返回实例A 再次请求IUserRepository 返回相同的实例A 结束请求 销毁作用域 释放所有Scoped实例 新请求 创建新作用域 请求IUserRepository 返回新实例B 请求1 DI容器 作用域 请求2 S2
使用方式
// 注册服务
builder.Services.AddScoped<IUserRepository, UserRepository>();

// ASP.NET Core 中间件中
app.Use(async (context, next) =>
{
    // 手动创建作用域
    using var scope = context.RequestServices.CreateScope();
    var repo = scope.ServiceProvider.GetService<IUserRepository>();
    await repo.LogRequestAsync(context.Request);
    await next();
});
核心特性
  1. 作用域内单例(同一作用域内实例共享)
  2. 跨请求隔离(不同请求不同实例)
  3. 自动释放(作用域结束时)
适用场景
  • 数据库上下文(如EF Core DbContext)
  • 请求级状态管理
  • 事务处理单元
// 典型应用:EF Core DbContext
public class AppDbContext : DbContext
{
    public DbSet<User> Users { get; set; }
}

// 注册
services.AddScoped<AppDbContext>();

// 在控制器中使用
public class UserController : Controller
{
    private readonly AppDbContext _context;

    public UserController(AppDbContext context)
    {
        _context = context; // 同一请求内共享实例
    }
}
优势
  1. 状态隔离:不同请求互不影响
  2. 资源优化:重用初始化成本高的对象
  3. 事务一致性:天然支持事务边界(整个请求)
劣势
  1. 作用域泄漏:意外在单例中引用会导致内存泄漏
// 错误示例:单例中引用Scoped服务
public class SingletonService
{
    private readonly IUserRepository _repo; // 危险!
    
    public SingletonService(IUserRepository repo)
    {
        _repo = repo; // 这会导致Scoped服务变成"伪单例"
    }
}
  1. 异步风险:在async/await中可能跨越不同作用域
  2. 测试复杂性:需模拟作用域环境

三、Singleton(单例生命周期)

原理
持有引用
1
DI容器
+SingletonCache
Singleton实例
+首次请求时创建
+全局唯一
使用方式
// 注册服务
builder.Services.AddSingleton<ICacheService, CacheService>();

// 预创建实例(立即初始化)
var cache = new CacheService();
builder.Services.AddSingleton<ICacheService>(cache);

// 延迟初始化
builder.Services.AddSingleton<IBackgroundService>(provider => 
    new BackgroundService(provider.GetRequiredService<ILogger>()));
核心特性
  1. 全局唯一实例(整个应用生命周期)
  2. 首次请求时创建(除非预注册实例)
  3. 应用关闭时释放
适用场景
  • 配置服务(如IOptions)
  • 内存缓存
  • 共享资源连接(如Redis连接池)
// 典型应用:内存缓存
public class MemoryCacheService : ICacheService, IDisposable
{
    private readonly ConcurrentDictionary<string, object> _cache = new();
    private Timer _cleanupTimer;

    public MemoryCacheService()
    {
        _cleanupTimer = new Timer(_ => Cleanup(), null, 0, 60_000);
    }

    public object Get(string key) => _cache.TryGetValue(key, out var value) ? value : null;

    public void Dispose() => _cleanupTimer?.Dispose();
}

// 注册
services.AddSingleton<ICacheService, MemoryCacheService>();
优势
  1. 性能最佳:单次初始化,零实例化开销
  2. 全局状态共享:跨请求共享数据
  3. 资源集中管理:如连接池、线程池
劣势
  1. 线程安全风险:需手动实现同步机制
public class CounterService
{
    private int _count = 0;
    
    // 危险:非线程安全
    public void Increment() => _count++;

    // 正确:线程安全版本
    public void SafeIncrement() => Interlocked.Increment(ref _count);
}
  1. 内存泄漏:意外持有引用导致GC无法回收
  2. 启动延迟:复杂单例初始化影响应用启动时间

三、生命周期对比分析

功能对比表
特性TransientScopedSingleton
实例创建时机每次请求作用域首次请求全局首次请求
实例数量多个每作用域一个全局一个
状态共享范围无共享作用域内共享全局共享
线程安全要求中等
适用场景无状态服务请求级状态全局共享资源
内存管理自动回收作用域结束时回收应用结束时回收
性能开销高(频繁创建)中等低(单次创建)
性能基准测试
BenchmarkDotNet=v0.13.1, OS=Windows 10
Intel Core i7-11800H 2.30GHz, 1 CPU, 16 cores

| 方法                | 调用次数 | 平均耗时 | 内存分配 |
|---------------------|---------|----------|----------|
| TransientResolve    | 10000   | 158 ns   | 32 B     |
| ScopedResolve       | 10000   | 76 ns    | 0 B      |
| SingletonResolve    | 10000   | 38 ns    | 0 B      |
典型错误案例

案例1:作用域泄漏

// 错误:单例中注入Scoped服务
builder.Services.AddSingleton<ReportService>();
builder.Services.AddScoped<DatabaseContext>();

// 解决方案1:使用工厂方法
builder.Services.AddSingleton<ReportService>(provider => 
    new ReportService(provider.GetRequiredService<DatabaseContext>));

// 解决方案2:改为作用域服务
builder.Services.AddScoped<ReportService>();

案例2:线程竞争

public class CacheService
{
    private Dictionary<string, object> _cache = new();
    
    // 错误:非线程安全
    public void Add(string key, object value)
    {
        _cache[key] = value;
    }

    // 正确:使用并发集合
    private ConcurrentDictionary<string, object> _safeCache = new();
    public void SafeAdd(string key, object value)
    {
        _safeCache[key] = value;
    }
}

案例3:资源未释放

public class FileService : IDisposable
{
    private FileStream _fileStream;

    public FileService()
    {
        _fileStream = File.Open("data.bin", FileMode.Open);
    }

    // 必须实现Dispose
    public void Dispose()
    {
        _fileStream?.Dispose();
    }
}

// 注册(Singleton需显式释放)
builder.Services.AddSingleton<FileService>();

四、生命周期决策树

graph TD
    A[新服务注册] --> B{是否有状态?}
    B -->|无状态| C[优先Transient]
    B -->|有状态| D{状态共享范围?}
    D -->|请求级| E[选择Scoped]
    D -->|应用级| F{是否线程安全?}
    F -->|是| G[选择Singleton]
    F -->|否| H[重构为线程安全或选Scoped]
    
    C --> I{创建成本高?}
    I -->|是| J[考虑Scoped]
    I -->|否| K[保持Transient]
    
    G --> L{需要立即初始化?}
    L -->|是| M[预注册实例]
    L -->|否| N[延迟初始化]
    
    E --> O[确保作用域边界]
    G --> P[实现IDisposable]

五、最佳实践指南

  1. 默认选择Transient

    • 除非有明确需求,否则优先无状态服务
    // 好:无状态服务使用Transient
    services.AddTransient<IValidator, EmailValidator>();
    
  2. Scoped生命周期黄金法则

    • 一个请求对应一个工作单元
    services.AddScoped<OrderProcessingService>();
    
  3. Singleton安全准则

    • 实现线程安全
    • 实现IDisposable
    • 避免依赖非Singleton服务
    public class SafeCache : ICache, IDisposable
    {
        private readonly ConcurrentDictionary<string, object> _store;
        private readonly Timer _timer;
        private readonly ReaderWriterLockSlim _lock = new();
    
        public void Dispose()
        {
            _timer?.Dispose();
            _lock?.Dispose();
        }
    }
    
  4. 生命周期验证

    // 启用容器验证
    var provider = services.BuildServiceProvider(validateScopes: true);
    
  5. 混合生命周期策略

    public class HybridService
    {
        // 长周期依赖Singleton
        private readonly ICache _cache;
        
        // 短周期依赖Transient工厂
        private readonly Func<ITransientService> _factory;
        
        public HybridService(
            ICache cache,
            Func<ITransientService> factory)
        {
            _cache = cache;
            _factory = factory;
        }
        
        public void Process()
        {
            // 按需创建Transient实例
            using var service = _factory();
            service.DoWork(_cache.GetData());
        }
    }
    

在这里插入图片描述

六、总结

  1. Transient:轻量级无状态服务的首选,但需警惕高频创建的性能开销
  2. Scoped:请求敏感资源(如数据库连接)的黄金标准,注意作用域边界
  3. Singleton:全局共享资源的最佳载体,但必须确保线程安全和资源释放

架构师建议:在大型系统中采用分层生命周期策略:

  • 基础设施层(缓存、配置):Singleton
  • 领域服务层:Scoped
  • 工具类/辅助服务:Transient

定期使用 .BuildServiceProvider(validateScopes: true) 检测生命周期错误,
这对预防生产环境的内存泄漏和状态污染至关重要。

在这里插入图片描述


原文地址:https://blog.csdn.net/sixpp/article/details/149133104

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!