必须用AddSingleton注册单例服务:①仅支持AddSingleton系列方法;②错用AddScoped/AddTransient会导致行为失控;③Singleton由根容器持有至应用退出,需自行管理IDisposable资源。

注册单例服务必须用 AddSingleton,不是“写对就行”,而是“只能这么写”
在 Program.cs 或 Startup.ConfigureServices 里,要把一个类型注册为单例,唯一正确的方式是调用 AddSingleton 系列方法。它有多个重载,但核心就三条路:
-
services.AddSingleton<IMyService, MyService>():接口 + 实现类,最常用 -
services.AddSingleton<MyService>():只传实现类,容器会把它当自身接口(MyService)来解析 -
services.AddSingleton<IMyService>(sp => new MyService(...)):手动控制实例创建,适合需要传参或依赖其他服务的场景
别用 AddScoped 或 AddTransient 代替,哪怕你“只打算用一次”。容器不会识别你的意图,它只认注册方式——错配生命周期,运行时不会报错,但行为完全不可控。
Singleton 和 Scoped 的根本区别不在“用几次”,而在“谁负责释放”
很多人以为“单例就是整个程序只建一次,作用域就是每个请求建一次”,这没错,但漏掉了关键点:内存管理和线程安全责任归属不同。
-
Singleton实例由根服务提供器(IServiceProvider)持有,直到应用退出才释放。如果你的服务里持有了IDisposable资源(比如HttpClient、文件流、数据库连接池管理器),必须自己实现IDisposable并在Dispose中清理——容器不会帮你调Dispose(除非你注册的是Func<T>工厂且返回IDisposable) -
Scoped实例由当前作用域(比如 ASP.NET Core 的HttpContext.RequestServices)持有,请求结束时自动调用Dispose(如果实现了)。所以DbContext必须用Scoped,否则连接不会及时释放,容易触发连接池耗尽 - 跨作用域访问
Scoped服务会直接抛异常:System.InvalidOperationException: Cannot resolve scoped service 'MyDbContext' from root provider.
误把 Scoped 当 Singleton 用,最常踩的坑是状态污染
典型场景:你在中间件或后台任务里,从根容器(app.Services)直接解析一个 Scoped 服务,比如:
var dbContext = app.Services.GetRequiredService<MyDbContext>(); // 错!
这会导致两个严重问题:
- 该
DbContext实例永远不会被释放,连接长期占用,数据库连接数飙升 - 多个并发请求共享同一个
DbContext实例,查询结果缓存、变更跟踪器(ChangeTracker)互相干扰,出现“查不到刚插入的数据”或“SaveChanges 报并发冲突” - 更隐蔽的是:如果你在
Singleton服务里注入了Scoped服务(比如日志装饰器里用了DbContext),启动时就会报错:“Cannot consume scoped service... from singleton”
判断该用 Singleton 还是 Scoped,先问自己三个问题
不是看“它会不会变”,而是看“它能不能被多个线程/请求同时安全使用”:
- 这个服务是否持有可变状态?比如缓存字典、计数器、配置快照。如果是,
Singleton必须加锁或用线程安全集合;Scoped天然隔离,不用操心 - 它是否封装了非线程安全的底层资源?比如
SmtpClient(.NET Framework)、某些旧版 SDK 客户端。这类服务绝不能注册为Singleton - 它的初始化开销是否极大?比如要加载几百 MB 配置、预热大型缓存。这种适合
Singleton,避免每次请求都重复初始化;轻量无状态工具类(如JsonSerializerOptions配置器)用Transient更干净
真正容易被忽略的,是 Singleton 服务里悄悄引用了 Scoped 依赖——编译不报错,运行时才暴露,而且错误现象分散、难以复现。


















