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

在这里插入图片描述

文章目录

      • 一、Transient(瞬时生命周期)
        • 原理
        • 使用方式
        • 核心特性
        • 适用场景
        • 优势
        • 劣势
      • 二、Scoped(作用域生命周期)
        • 原理
        • 使用方式
        • 核心特性
        • 适用场景
        • 优势
        • 劣势
      • 三、Singleton(单例生命周期)
        • 原理
        • 使用方式
        • 核心特性
        • 适用场景
        • 优势
        • 劣势
      • 三、生命周期对比分析
        • 功能对比表
        • 性能基准测试
        • 典型错误案例
      • 四、生命周期决策树
      • 五、最佳实践指南
      • 六、总结

一、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(作用域生命周期)

原理
请求1DI容器作用域请求2S2开始请求创建作用域请求IUserRepository创建新实例返回实例A再次请求IUserRepository返回相同的实例A结束请求销毁作用域释放所有Scoped实例新请求创建新作用域请求IUserRepository返回新实例B请求1DI容器作用域请求2S2
使用方式
// 注册服务
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);}// 必须实现Disposepublic void Dispose(){_fileStream?.Dispose();}
}// 注册(Singleton需显式释放)
builder.Services.AddSingleton<FileService>();

四、生命周期决策树

graph TDA[新服务注册] --> 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
    {// 长周期依赖Singletonprivate 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) 检测生命周期错误,
这对预防生产环境的内存泄漏和状态污染至关重要。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/88051.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/88051.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件工程经济与伦理

前言 各位帅哥美女&#xff0c;能看到这篇博客的都有口福了&#xff0c;学习这门课程就像遨游在大份的海洋&#xff0c;一不小心就吃上一口。能看到这篇博客说明我们是有缘人可以点赞收藏一下&#xff0c;这篇博客可以在你无比饥饿的时候给你送上一坨&#xff01;&#xff08;香…

AI 体验走查 - 火山引擎存储的 AI UX 探索之路

01 概述 火山引擎存储技术团队驱动 AI 自主完成用户体验走查 / 可用性测试的执行与评价&#xff0c;帮助业务改善交互体验。 立项“故事走查”的背景诉求和 AI 机遇 如何搭建“AI 评价”能力&#xff0c;精准识别交互问题 让交互体验故事走查变为技术产品&#xff0c;讲解系…

【世纪龙科技】汽车零部件检验虚拟实训室-助力汽车职教实训

在汽车产业加速向电动化、智能化转型的背景下&#xff0c;职业院校汽车专业教学面临新的挑战&#xff1a;传统实训受限于设备数量不足、操作风险高、标准化程度低等问题&#xff0c;导致学生实践机会有限&#xff0c;技能掌握不扎实。如何让学生在有限资源下高效掌握零部件检验…

MySQL常用操作 查看表描述以及表结构、连接数及缓存和性能指标

查看表描述以及表结构查看数据库名SHOW DATABASES; SELECT DATABASE(); SELECT DATABASE() AS current_database;查看数据库中表的列表SHOW TABLES; SELECT TABLE_NAME, TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA your_database_name; SELECT TABLE_NA…

音视频学习(三十六):websocket协议总结

概述项目描述标准RFC 6455使用端口默认 80&#xff08;ws&#xff09;&#xff0c;443&#xff08;wss&#xff09;基于协议TCP特性全双工、低开销、持久连接、可穿透代理特点 全双工通信&#xff1a; WebSocket 允许客户端和服务器之间建立一个持久的连接&#xff0c;并且数据…

docker版本nacos的搭建

1.下载镜像2.拷贝出容器中对应的配置文件&#xff0c;logs&#xff0c;data&#xff0c;conf3.编写yaml配置文件version: 3.8 services:nacos-server:image: nacos/nacos-server:v2.4.0container_name: nacos-serverrestart: unless-stoppedports:- "8848:8848" # …

【机器学习深度学习】 如何解决“宏平均偏低 / 小类识别差”的问题?

目录 &#x1f9e9; 场景 一、先问清楚&#xff1a;小类差&#xff0c;到底差在哪&#xff1f; 二、对症下药&#xff1a;六大优化策略&#xff08;分类任务专用&#xff09; ✅ 1. 处理类别不平衡&#xff08;最常见&#xff09; ✅ 2. 优化数据质量 ✅ 3. 更强的模型结…

数据结构 --- 栈

栈 --- stack前言一、栈结构二、相关方法1.初始化2.入栈3.出栈4.判空5.获取栈顶元素6.获取栈大小7.销毁前言 栈是一个特殊的线性表&#xff0c;遵循一个先进后出的特性&#xff0c;即操作数据&#xff08;入栈&#xff0c;出栈&#xff09;只能从栈顶操作&#xff0c;栈底是一…

【uniapp】---- 在 HBuilderX 中使用 tailwindcss

1. 前言 接手了一个uniapp的微信小程序项目,因为在上一个 taro 的项目中使用的 tailwindcss,感觉比较方便,又不想动项目中原来的代码,因此就配置 tailwindcss,在新创建的子包中使用。 2. 分析 vue2 版本的 uni-app 内置的 webpack 版本为 4 , postcss 版本为 7, 所以还是…

Spring Boot + Easy Excel 自定义复杂样式导入导出

tips&#xff1a;能用模板就用模板&#xff0c;当模板不适用的情况下&#xff0c;再选择自定义生成 Excel。官网&#xff1a;https://easyexcel.opensource.alibaba.com安装<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</arti…

Spark从入门到实战:安装与使用全攻略

目录一、Spark 简介1.1 Spark 的概念1.2 Spark 的优势1.3 Spark 的应用场景二、安装前准备2.1 硬件要求2.2 软件要求2.3 下载 Spark三、Spark 安装步骤3.1 解压安装包3.2 配置环境变量3.3 配置 spark-env.sh3.4 配置 slaves 文件&#xff08;分布式模式&#xff09;3.5 启动 Sp…

Python 进程间的通信:原理剖析与项目实战

在 Python 编程中,当涉及多进程编程时,进程间的通信(Inter-Process Communication,简称 IPC)是一个重要的课题。多个进程在运行过程中,常常需要交换数据、传递状态或协同工作,这就离不开进程间通信机制。本文将深入讲解 Python 进程间通信的原理,并结合实际项目案例,展…

神经网络之BP算法

一、正向传播正向传播&#xff08;Forward Propagation&#xff09;是神经网络中数据从输入层流向输出层的过程。输入数据通过各层的权重和激活函数逐层计算&#xff0c;最终得到预测输出。数学表示&#xff1a; 对于第 ( l ) 层的神经元&#xff0c;其输出计算如下&#xff1a…

Ubuntu 版本号与别名对照表(部分精选)

Ubuntu 的别名遵循 形容词 动物名 的命名规则&#xff0c;且两个单词首字母相同&#xff0c;按字母表顺序循环使用&#xff08;从 Ubuntu 6.06 开始&#xff09;。 &#x1f4c5; Ubuntu 版本号与别名对照表&#xff08;部分精选&#xff09; 版本号别名 (开发代号)发布时间…

实验03-Spark批处理开发

使用Spark Shell探索RDD 启动并使用Scala Spark Shell 在终端窗口&#xff0c;启动Scala Spark shell&#xff1a; spark-shell --master local查看对象&#xff1a; scala> sc scala> spark输入spark.[TAB]然后可以看到所有可用的方法。 读并显示文本文件 查看文本…

【R语言】Can‘t subset elements that don‘t exist.

Error in select(): ℹ In argument: all_of(label_col). Caused by error in all_of(): ! Cant subset elements that dont exist. ✖ Element Label doesnt exist. Run rlang::last_trace() to see where the error occurred.原文中文解释涉及关键词Error in select()报错发生…

Spring的依赖注入(xml)

引入 首先先明白&#xff0c;依赖注入描述的是在容器中建立bean与bean之间的依赖关系&#xff0c;本质就是将一个类中和别的类解耦的方式&#xff0c;就是把别的类&#xff0c;写在成员变量位置&#xff0c;再对外提供可以给成员变量赋值的方法&#xff0c;外界就直接调用来给…

docker运行的一些常用命令

docker images 显示可以加载的镜像docker ps 显示运行的docker容器 加-a显示所有的容器docker run --name 容器名字 -d 镜像名字docker start 容器名/ID 开启容器docker stop 容器名/ID 关闭容器docker exec -it dock…

Django跨域

步骤 1&#xff1a;安装 django-cors-headerspip install django-cors-headers步骤 2&#xff1a;修改 Django 配置 在 settings.py 中添加&#xff1a;INSTALLED_APPS [...,"corsheaders", # 新增 ]MIDDLEWARE [...,"corsheaders.middleware.CorsMiddleware…

20250706-10-Docker快速入门(下)-Harbor镜像仓库_笔记

一、Harbor镜像仓库搭建与使用1. Harbor概述&#xfeff;&#xfeff;定义: 由VMWare公司开源的容器镜像仓库系统技术基础: 在Docker Registry基础上进行企业级扩展核心特性:提供管理用户界面(GUI)基于角色的访问控制(RBAC)支持&#xfeff;AD/LDAP\mathrm{AD}/\mathrm{LDAP}AD…