.NET依赖注入(三种生命周期)
.NET依赖注入
什么是依赖注入
依赖注入是一种设计模式,它让类的依赖项(比如服务、数据库、日志组件等)由外部系统(比如框架)提供,而不是类自己创建。
常见术语
Service: 被注入的服务(类、接口)
Container: 注册和管理服务的容器
Lifetime: 服务的生命周期(Scoped、Singleton、Transient)
服务生命周期
依赖注入容器管理服务的生命周期(Lifetime),决定了服务实例的创建和销毁时机。
Scoped(作用域)
定义:在一个请求(Scope)中创建一个实例。通常在 Web 应用中表示一次 HTTP 请求周期。
注册方式:services.AddScoped<TService, TImplementation>()
适用场景:适用于需要在一个请求中保持状态,但请求之间不共享的服务,例如工作单元(Unit of Work)。
比喻:
1 | services.AddScoped<IMyService, MyService>(); |
说明:在同一次 HTTP 请求中注入的都是同一个 MyService 实例,不同请求是不同实例。
想象你是一个饭店老板:
每进来一个顾客(表示一次 HTTP 请求):
给他分配一个【托盘】(表示一个作用域 Scope)。
然后在托盘上放饮料、菜单(服务对象)。
Scoped 生命周期就像【每个顾客都有自己的托盘,托盘里的服务对这个顾客是一样的】:
顾客 A(HTTP 请求 1)来了,系统给他一个新的 MyService 实例。
顾客 A 用了一整顿饭(这个请求过程中),无论 Controller / Service 层调用几次,拿到的都是这个实例。
顾客 B(HTTP 请求 2)又来了,他也分配了一个新的 MyService 实例,跟 A 的不同。
例子
在 ASP.NET Core Web 项目中使用它来验证“同一个请求用同一个实例”的效果。
1 | public interface IMyScopedService |
注册服务到容器中 Program.cs
1 | builder.Services.AddScoped<IMyScopedService, MyScopedService>(); |
1 | [ ] |
测试效果
启动项目,访问 /test/check
会看到输出如下(两个 ID 一样,说明是同一个实例):
1 | { |
刷新页面(即另一个请求),会得到新的 ID:
1 | { |
说明:
每次请求都会创建新的 MyScopedService 实例。
同一个请求内的多个注入是同一个实例
Singleton(单例)
定义:整个应用程序生命周期中只创建一个实例,并被所有请求共享。
注册方式:services.AddSingleton<TService, TImplementation>()
适用场景:线程安全、无状态服务,或有状态但共享全局状态的服务。
比喻理解 Singleton 生命周期(单例)
饭店老板的例子(继续)
还记得之前说的“每个顾客一个托盘”是 Scoped 吗?
现在换成 Singleton:
就像你饭店里有一台共享的饮水机(或菜单看板)☕,所有顾客都用同一个,不会为每个顾客新建一个。
技术上:Singleton 就是 “只创建一次服务实例”,并在整个程序中共享。
1 | services.AddSingleton<IMyService, MyService>(); |
说明:第一次请求时创建 MyService 的实例,之后每次都使用同一个实例。
例子
和之前的 Scoped 示例相比,我们只改注册方式:
1 | public interface IMySingletonService |
注册为 Singleton(Program.cs)
1 | builder.Services.AddSingleton<IMySingletonService, MySingletonService>(); |
1 | [ ] |
访问测试:
打开浏览器访问 /test/singleton:
1 | { |
再次刷新页面,你会发现输出的还是同一个 ID → 说明所有请求用的是同一个服务实例
Transient(瞬态)
定义:每次请求都会创建新的实例。
注册方式:services.AddTransient<TService, TImplementation>()
适用场景:轻量级、无状态服务;适合短生命周期的对象。其缺点是生成的对象比较多,会浪费内存。
示例:
1 | services.AddTransient<IMyService, MyService>(); |
说明:每次注入时都创建一个新的 MyService 实例。
生命周期的适配策略
场景 推荐生命周期
全局配置服务 Singleton
数据库上下文(DbContext) Scoped
工具类/轻量服务 Transient
缓存服务(如 Redis 缓存) Singleton
日志记录器 Singleton
用户请求上下文相关服务 Scoped
注意事项
不要在 Singleton 中注入 Scoped 或 Transient
会导致作用域错误或内存泄漏。
如果一定要这么做,可以使用 IServiceScopeFactory 手动创建作用域。
EF Core 的 DbContext 默认是 Scoped 生命周期
所以应避免把 DbContext 注入到 Singleton 服务中。
比喻理解 Transient(瞬态)
继续用饭店的例子:
Transient 就像你给顾客倒水时每次都换一个一次性纸杯,谁要用水,立即临时给他一个新的杯子,用完就扔掉,永远不会复用
例子
1 | public interface IMyTransientService |
注册为 Transient(Program.cs)
1 | builder.Services.AddTransient<IMyTransientService, MyTransientService>(); |
1 | [ ] |
1 | { |
注意:同一个请求中注入两次服务,拿到的是两个不同实例!
再刷新页面,ID 还会变 → 说明每次请求都会 new。
三种生命周期对比总结
生命周期 创建频率 使用范围 举例类比
Singleton 应用启动时创建一次 所有地方都复用 饮水机 / 菜单看板
Scoped 每次请求创建一次 当前请求中复用 每人托盘
Transient 每次注入都创建新实例 无复用,每次都 new 一次性纸杯