.NET IServiceProvider如何获取已创建瞬态服务实例且不生成新实例
核心结论
.NET 内置依赖注入容器原生不支持查询已创建的瞬态生命周期服务实例,这是容器的设计决定,不是使用方式错误:
- 瞬态(Transient)生命周期的规则就是每次向容器发起服务解析请求时,都会创建全新的服务实例
- 对比单例(Singleton)、作用域(Scoped)生命周期,容器会持有后两类服务的实例引用直到对应生命周期结束,但瞬态服务容器不会保存任何引用,实例生命周期完全由消费方控制,容器本身没有已创建瞬态实例的列表存储
- 你尝试的构造函数注入
IEnumerable<ISomeService>、GetServices<ISomeService>()、GetRequiredServices<ISomeService>()本质都是向容器发起服务解析请求,必然触发新实例创建,符合预期行为。
实现方案
要实现“不创建新实例、获取所有已存在瞬态实例”的需求,需要自行实现实例跟踪逻辑,推荐侵入性最低的实现方式如下:
步骤1:实现无内存泄漏的瞬态实例跟踪器
使用ConditionalWeakTable存储实例引用,不会阻止实例被垃圾回收,避免内存泄漏:
using System.Runtime.CompilerServices; public class TransientInstanceTracker { // 键为被跟踪的瞬态实例,值无实际意义,仅利用该类型的弱引用特性 private readonly ConditionalWeakTable<object, object> _trackedInstances = new(); /// <summary> /// 记录新创建的瞬态实例 /// </summary> public void Track(object serviceInstance) { if (serviceInstance == null) return; _trackedInstances.TryAdd(serviceInstance, null!); } /// <summary> /// 获取指定类型所有已创建、且未被GC回收的瞬态实例 /// </summary> public IEnumerable<TService> GetExistingInstances<TService>() { foreach (var entry in _trackedInstances) { if (entry.Key is TService service) { yield return service; } } } }
步骤2:注册服务时接入跟踪逻辑
将跟踪器注册为单例,修改瞬态服务的注册方式,在实例创建时自动加入跟踪:
public void ConfigureServices(IServiceCollection services) { // 注册单例跟踪器 services.AddSingleton<TransientInstanceTracker>(); // 替换原有的AddTransient注册,通过工厂创建实例时自动跟踪 services.AddTransient<ISomeService, SomeService>(sp => { var service = new SomeService(); sp.GetRequiredService<TransientInstanceTracker>().Track(service); return service; }); }
步骤3:查询已存在实例
需要获取实例列表时,直接注入跟踪器调用查询方法即可,整个过程不会触发新实例创建:
public class AnotherService { public AnotherService(TransientInstanceTracker tracker) { // 获取所有已存在的ISomeService瞬态实例,无新实例生成 IEnumerable<ISomeService> existingInstances = tracker.GetExistingInstances<ISomeService>(); } }
注意:不要使用
List<object>、HashSet<object>等强引用集合存储瞬态实例,这类集合会持有实例的强引用,导致实例永远无法被垃圾回收,最终引发内存泄漏。
其他可选方案
如果不想自行实现跟踪逻辑,也可以替换内置DI容器为第三方DI框架(如Autofac、Unity等),但绝大多数第三方DI容器默认也不会跟踪瞬态实例,需要手动开启实例跟踪配置,且同样需要注意弱引用、内存泄漏相关问题。
内容的提问来源于stack exchange,提问作者monty
相关产品推荐
相关产品推荐

