AddSingleton - 生命周期 - 类需要实现IDisposable吗?

3

在使用AddScopedAddSingleton添加服务时,该服务是否需要实现IDisposable接口(即使它没有使用任何非托管资源如文件)?

这里是从Microsoft文档中找到的示例:

// Services that implement IDisposable:
public class Service1 : IDisposable {}
public class Service2 : IDisposable {}
public class Service3 : IDisposable {}

public interface ISomeService {}
public class SomeServiceImplementation : ISomeService, IDisposable {}

public void ConfigureServices(IServiceCollection services)
{
    // The container creates the following instances and disposes them automatically:
       services.AddScoped<Service1>();
       services.AddSingleton<Service2>();
       services.AddSingleton<ISomeService>(sp => new SomeServiceImplementation());

    // The container doesn't create the following instances, so it doesn't dispose of
    // the instances automatically:
       services.AddSingleton<Service3>(new Service3());
       services.AddSingleton(new Service3());
}

如果我有这段代码会发生什么:

public class Service0  // (doesn't implement Disposable) 

services.AddScoped<Service0>();   // what happens to it when request scope ends? Does it stay on the heap?

services.AddSingleton<Service0>();   // it lives till application dies
services.AddSingleton(new Service0());  // ??
services.AddSingleton<IConfigureOptions<Service0>>((ctx) =>
   {
          return new ConfigureNamedOptions<Service0>(null, (config) =>
   {
// Do something here -- in debug mode it is executing this logic for each request
}}  // it is returning "new Service0" when a request is made. Does it mean for each request it returns new object and keeps in heap memory or returns same previously created object?

那个 Microsoft 文档示例只是试图让你了解容器在处理可释放组件时的行为。它并不意味着告诉你所有组件都必须实现 IDisposable 接口。相反,必须实现 IDisposable 应该是异常而非规范。 - Steven
2个回答

6
服务需要实现IDisposable吗(即使它没有使用任何非托管资源,如文件)?
通常不需要,因为IDisposable的主要目的是允许释放非托管资源。然而,实现IDisposable还有一个额外的原因。Dispose()方法有时被用作在构造函数中启动操作的钩子。例如,构造函数开始持续时间测量,而Dispose()停止测量并将持续时间报告给某个监控机制。
关于IDisposable的背景知识
如果对象不实现IDisposable,则不意味着它保留在堆中。事实上,垃圾回收器甚至不知道IDisposable是什么。这个接口只是一种模式。然而,编译器知道IDisposable,并且它会在using语句范围的末尾发出调用Dispose()的语句。
此外,在许多情况下,基础结构层或库(如ASP.NET Core中的DI)检查对象是否实现了IDisposable,如果确实实现了,就会调用Dispose()。
因此,一个对象实现IDisposable并不保证Dispose()在GC之前被调用。它取决于对象的用户。要确保在GC之前实际上调用了Dispose(),完整的可处理模式实现包括从“析构函数”调用Dispose()。
请求作用域结束时它会发生什么?保留在堆中吗?请求作用域结束时它会发生什么?
AddScoped<Service0>(): 在请求结束时,对象的引用被“遗忘”(GC可以随时删除它)。在遗忘引用之前,将检查对象是否实现了IDisposable,并且如果实现了,将在其上调用Dispose()。
AddSingleton<Service0>(): 在Web主机生命周期结束时,对象的引用被“遗忘”(GC可以随时删除它)。在遗忘引用之前,将检查对象是否实现了IDisposable,并且如果实现了,将在其上调用Dispose()。
AddSingleton(new Service0()):在Web主机生命周期结束时,对象的引用被“遗忘”(GC可以随时删除它)。但由于该对象是从外部提供而不是由DI实例化的,因此不会检查是否有IDisposable并且不会调用Dispose。

由于您的列表中缺少此项,因此提供一个返回 IDisposable 的工厂方法也将调用 Dispose,即使该工厂返回一个“静态”实例。例如,services.AddSingleton(_ => new Service0());var s0 = new Service0(); services.AddSingleton(_ => s0); 都将被处理(如果它们实现了 IDisposable)。 - pinkfloydx33
关于 AddSingleton(new Service0()) 的问题:对于这个语句中的“它不会被检查为 IDisposable”,我感到困惑,正如 @pinkflyoedx33 所提到的,如果我们实现了“IDisposable”,它将被清理,唯一的问题是 GC 不会自动清理此对象。如果我错了,请纠正我。另一个问题是 services.AddSingleton<IConfigureOptions<Service0>>((ctx) => {return new ConfigureNamedOptions<Service0>(null, (config) => {// 在这里做些什么}}。这意味着什么?每次新请求都会返回“new service0”。我以为它只在第一次创建,后续请求会忽略它。 - user2608601
在我的应用程序中,“新服务0”处理与IConfigurationHost的交互,后者负责读取配置文件并内部触发PhysicalFilesWatcher的ChangeToken。只是想知道,如果为每个请求创建“新服务0”,是否会触发过多的ChangeToken。 - user2608601

0

IDisposable只是一个接口,它使实现类有机会在对象销毁时进行一些清理工作,它本身不做任何事情。 DI会根据它们的生存期销毁实例,如scoped、transient、Singleton,对象在销毁后是否存在于堆上,由垃圾收集器决定。 如果在Singleton中定义新的实例,则该对象将随着Singleton实例一起被销毁,并由于Singleton的生存期直到应用程序的生存期结束,因此它将遵循其父级的生存期,除非您在那里执行某些非托管操作。


services.AddScoped<someservice>(); 当我发出http请求时,在内存转储中,我可以看到对象计数增加了,但是即使我调用GC.collect,这个计数也不会减少。我以为GC.collect会强制减少对象计数器,或者是某个子对象阻止了该对象的销毁(我还处置了子对象以更安全),或者是由GC决定何时清理它。不确定我哪里错了FYI:子对象涉及EntityFramework上下文 - user2608601
实体框架的数据库上下文的作用域是什么? - Ali Alp
我理解默认情况下DBcontext始终作为范围上下文工作。services.AddDbContext<someContext>((options) => { var connectionFactory = services.BuildServiceProvider().GetService<IConnectionFactory>(); options.UseSqlServer(connectionFactory.GetConnectionString("somecdbconnection")); }); contextFile中有这段代码:private static Dictionary<Type, List<Tuple<Type, string, string>>> tempdic= new Dictionary<Type, List<Tuple<Type, string, string>>>(); DbSet<someEntity> training{ get; set; } - user2608601

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接