如何确定类的作用域:Singleton还是Scoped?两类工具类分析
依赖注入作用域问题分析
代码对比
MetricsUtility 实现
MetricsUtility : IMetricsUtility { var context; MetricsUtility (ServiceProvider, Context) { Context = ServiceProvider.GetRequiredService(Context); } function Write() { Console.Write($"The context is {Context}"); } }
HardwareUtility 实现
HardwareUtility : IHardwareUtility { HardwareUtility() { } function Write(Context) { Console.Write($"The context is {Context}"); } }
问题
已知Context在每个线程中均不同,请问MetricsUtility和HardwareUtility应设为Singleton还是Scoped作用域?确定类作用域需考量哪些因素?
我的理解如下:若将MetricsUtility声明为Singleton,它会作为全局变量,由于构造函数中注入了Context,多线程同时访问时会导致行为不一致;而HardwareUtility的Context作为函数参数传入,即使声明为Singleton,每个线程传入自身的Context,多线程调用Write方法也不会出现不一致。
请问该理解是否正确?除变量(如Context)的作用域外,还有哪些考量因素或设计模式需要关注?
解答
你的理解完全正确:
- MetricsUtility必须设为Scoped作用域。因为它在构造阶段就注入了
Context,如果用Singleton,这个Context会是实例化时的那个线程的上下文,后续其他线程调用Write时都会复用这个旧的Context,必然导致线程间上下文混乱,行为不一致。 - HardwareUtility可以安全地设为Singleton。它的
Context是每次调用Write时通过参数传入的,自身不持有任何线程相关的状态,属于无状态服务,Singleton模式下也能保证多线程调用时各自使用正确的上下文,不会有冲突。
除了变量作用域,确定类作用域还要考量这些因素:
- 服务的状态持有情况:如果服务持有可变状态(比如用户会话、请求上下文),必须用Scoped或Transient;无状态服务优先用Singleton,减少实例创建开销。
- 依赖的作用域:如果服务依赖Scoped类型的对象,那这个服务不能设为Singleton,否则会出现“捕获Scoped依赖”的问题——Singleton服务会一直持有这个Scoped依赖,导致它无法被正确释放,甚至引发上下文错乱。
- 性能开销:Singleton实例只会创建一次,性能最优;Transient每次调用都创建新实例,开销最大;Scoped在每个作用域内复用实例,开销介于两者之间。如果服务初始化成本高,优先考虑Singleton(前提是无状态)。
- 线程安全性:即使是无状态服务,也要考虑其方法内部的线程安全逻辑。比如如果方法内有共享的静态变量操作,即使是Singleton也要保证线程安全(用锁或线程安全的数据结构)。
- 设计模式适配:比如使用工厂模式时,工厂类可以是Singleton,而创建的实例根据需求设为Scoped或Transient;或者使用装饰器模式时,装饰器的作用域通常要和被装饰对象保持一致,避免出现作用域不匹配的问题。
内容的提问来源于stack exchange,提问作者Aastha Paruthi
相关产品推荐
相关产品推荐

