【问题标题】:Why is there a separation of concerns between Finalize and Dispose methods? [duplicate]为什么 Finalize 和 Dispose 方法之间存在关注点分离? [复制]
【发布时间】:2017-12-03 09:22:15
【问题描述】:

来自实现 Finalize 的 MSDN 文章;

对于使用非托管资源(例如文件句柄或数据库连接)的类,您应该重写 Finalize,当使用它们的托管对象在垃圾回收期间被丢弃时必须释放这些资源。

来自实现 IDisposible.Dispose 的 MSDN 文章;

使用此方法关闭或释放实现此接口的类的实例所持有的非托管资源,例如文件、流和句柄。按照惯例,此方法用于与释放对象持有的资源或准备对象以供重用相关的所有任务。

即使在每篇(详尽的)文章的上下文中,这似乎也是一个非常模棱两可的定义。

我真正失去 Finalize 目的的地方是这里;

因为垃圾收集是非确定性的,所以您无法准确知道垃圾收集器何时执行终结。要立即释放资源,还可以选择实现 dispose 模式和 IDisposable 接口。 您的类的使用者可以调用 IDisposable.Dispose 实现以释放非托管资源,并且您可以使用 Finalize 方法在未调用 Dispose 方法的情况下释放非托管资源。

我是否应该同时实现两者,并在消费应用程序忘记处置我的对象时强制自己完成?

与 GC 如此密切地打交道对我来说是新的……我想确保我正确地放弃了我的资源。但是,我不完全理解为什么这两种都存在,或者何时使用每一种。

【问题讨论】:

  • 第一句话说“你应该为使用 非托管 资源的类重写 Finalize”。因此,如果您没有任何非托管资源,则不要覆盖 Finalize。
  • 如果消费应用程序忘记释放你的资源,这是他们的问题。 GC 收集可能会立即发生,也可能会在 5 分钟后发生。 Dispose 在同一个线程上运行的目标是尽快释放资源(比如说文件句柄),以便其他人可以使用它。
  • @user3185569 GC 不处理非托管资源。 根据定义确实如此。非托管资源的定义是不被GC管理的资源。
  • 通常您会覆盖终结器,以便它调用 Dispose。实现 Dispose 的默认模板说明了这一点,并在方法中提供有关终结器或用户代码是否触发了 dispose 方法的信息。此外,Dispose 方法是确定性的,而您永远不知道何时调用终结器...
  • 您实际上有哪些非托管资源需要处理?

标签: c# garbage-collection resources dispose finalize


【解决方案1】:

使用 Dispose 模式实现两者。 Visual Studio 甚至会为您搭建脚手架。 Dispose 允许您在使用完稀缺资源后立即剥离它们。如果消费者出于某种原因忘记调用您的 Dispose 方法,但允许他们的类实例超出范围,则 Finalizer 允许您处理它们。

【讨论】:

  • 几乎任何人实现终结器几乎总是错误的。如果您正在为任何类型实现终结器,那么您几乎肯定不应该这样做。
  • 抱歉,这完全不正确。 MSDN .net 平台指南框架设计指南在docs.microsoft.com/en-us/dotnet/standard/design-guidelines/… 处告诉您如何实现 Dipose 模式。如果您实施 Dispose,则应始终实施 Finalize。如果你在没有 dispose 的情况下实现 Finalize,或者在没有 Finalize 的情况下进行 dispose - 那么你做错了。
  • 你应该阅读你自己的链接,因为它与你自己的陈述相矛盾。它描述了什么时候适合,什么时候不适合拥有终结器,什么时候适合,什么时候不适合实现IDisposable。对于大多数 C# 开发人员来说,实现IDisposable 的情况可能经常出现。任何 C# 开发人员几乎都不会遇到适合添加终结器的情况。如果您正在编写终结器,那么您做错事的可能性非常高。
  • 如果你的意思是“不要exlcusively finalize”,那么是的,但是如果你实现了Dispose模式,你总是会实现一个终结器——即使VS.net也是这样的。您始终可以在 Dispose 中抑制终结器(无论如何都是最佳实践)。如果你在没有终结器的情况下实现 Dispose,那么你就不能很好地保护你的非 CLR 资产——考虑一个嵌入到你的类中的简单 Bitmap 对象;你可以在你的类被 Disposed 时 Dispose 它,但是如果你的调用者忘记了 Dispose()/using{} 你的 Finalizer 就是在后面的 GDI 故障中的唯一障碍。
  • 不,完全错了。您不会在终结器中处理 Bitmap。您永远不会在 fnzalizer 中接触任何其他托管对象。您只能使用终结器直接(而不是通过其他托管对象间接)与非托管资源进行交互。在终结器中访问另一个托管对象是不安全的。在您的示例中,由于对象具有要直接清理的非托管资源,因此它与终结器无关,它应该简单地处理Dispose 中的位图并且没有终结器。
【解决方案2】:

如果您有非托管资源,只需像这样实现IDisposable 模式:

public class ApiClient : IDisposable 
{  
    private readonly HttpClient http = new HttpClient();

    public void Dispose()
    {  
        Dispose(true);  
        GC.SuppressFinalize(this);  
    }  

    protected virtual void Dispose(bool disposing)
    {  
        if (disposing)
        {
            http?.Dispose();  
        }  
    }  
}

阅读更多关于一次性图案here。我永远不会忘记对我的一次性对象使用using 语句,以确保调用 dispose 方法:

using (var api = new ApiClient()) 
{
    // use api
}

【讨论】:

  • 没有理由禁止没有终结器的对象的终结器。当你没有终结器时,也没有理由有一个参数来指示对象是否被释放到终结器中。
  • 现在你有一个对象的终结器,它实际上并没有任何事情要做。您不应该将终结器添加到在其终结器中实际上没有任何需要清理的对象。
  • @Servy 我很想看到你的正确答案
  • 查看副本,它有很多关于这个主题的好答案。
猜你喜欢
  • 2022-01-18
  • 1970-01-01
  • 2011-04-08
  • 2010-10-12
  • 2014-09-13
  • 2011-05-28
  • 2023-04-10
相关资源
最近更新 更多