【问题标题】:Why should we call SuppressFinalize when we don't have a destructor当我们没有析构函数时为什么要调用 SuppressFinalize
【发布时间】:2010-04-09 06:14:11
【问题描述】:

我有几个问题我无法得到正确答案。

1) 当我们没有析构函数时,为什么要在 Dispose 函数中调用 SuppressFinalize。

2) Dispose 和 finalize 用于在对象被垃圾回收之前释放资源。无论是托管资源还是非托管资源,我们都需要释放它,那么为什么我们需要在 dispose 函数中添加一个条件,当我们从 IDisposable:Dispose 调用这个被覆盖的函数时说 pass 'true' 并在从 finalize 调用时传递 false 。

请看下面我从网上复制的代码。

class Test : IDisposable
   {
     private bool isDisposed = false;

     ~Test()
     {
       Dispose(false);
     }

     protected void Dispose(bool disposing)
     {
       if (disposing)
       {
         // Code to dispose the managed resources of the class
       }
       // Code to dispose the un-managed resources of the class

       isDisposed = true;
     }

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

如果我删除布尔受保护的 Dispose 函数并实现如下所示。

   class Test : IDisposable
   {
     private bool isDisposed = false;

     ~Test()
     {
       Dispose();
     }


     public void Dispose()
     {
      // Code to dispose the managed resources of the class
      // Code to dispose the un-managed resources of the class
      isDisposed = true;

      // Call this since we have a destructor . what if , if we don't have one 
       GC.SuppressFinalize(this);
     }
   }       

【问题讨论】:

    标签: c# .net garbage-collection finalizer suppressfinalize


    【解决方案1】:

    我在这里有点冒险,但是...大多数人不需要需要成熟的处置模式。在直接访问非托管资源(通常通过IntPtr)和面对继承时,它的设计是可靠的。大多数情况下,实际上都不需要这些。

    如果您只是持有对实现 IDisposable 的其他东西的引用,那么您几乎可以肯定不需要终结器 - 直接持有资源的任何东西都负责处理它。你可以这样做:

    public sealed class Foo : IDisposable
    {
        private bool disposed;
        private FileStream stream;
    
        // Other code
    
        public void Dispose()
        {
            if (disposed)
            {
                return;
            }
            stream.Dispose();
            disposed = true;
        }
    }
    

    请注意,这不是线程安全的,但这可能不会成为问题。

    通过不必担心子类直接持有资源的可能性,您不需要抑制终结器(因为没有终结器) - 并且您不需要提供子类自定义处置的方法任何一个。没有继承,生活更简单。

    如果您确实需要允许不受控制的继承(即您不愿意打赌子类会有非常特殊的需求),那么您需要采用完整的模式。

    请注意,使用 .NET 2.0 中的 SafeHandle,与 .NET 1.1 中相比,您需要自己的终结器甚至更少。


    首先要解决您关于为什么有disposing 标志的观点:如果您在终结器中运行,则您引用的其他对象可能已经终结。你应该让他们自己清理,你应该只清理你直接拥有的资源。

    【讨论】:

    • 嗨乔恩,只是吹毛求疵,但句子“任何拥有资源的东西都可以直接处理”,应该是“将处理那个”,(即“可以”->“将") 强调一点,处理它根本不是外部类的工作。
    • 还有一个问题,由于 System.Object 是所有对象的基础,默认情况下实现了 finalize 方法,即使我们没有提供析构函数,GC 也不会将其放入 finalize 队列?或者为什么我们说如果我们不提供 destrutor 对象就不会被放入 finalize 队列?因为继承保护成员就像派生类的私有成员。
    • 如果父类没有,那么派生自非平凡父类的子类是否有任何正当理由具有清理终结器?我想不出派生类将任何非托管资源封装到它们自己的类中并使其自己的终结器完全与主资源分离的任何情况。实际上,即使人们想要的只是一个“警钟”终结器,最好将 那个 封装到它自己的类中,而不是在派生类中添加终结器。
    • @supercat:是的,可能就是这样……虽然“警钟”位需要在每个对象中添加一个额外的字段,否则可以避免。
    • 我会比“大多数人不需要成熟的处置模式”更进一步:没有人需要它,因为将需要清理的托管和非托管资源直接作为字段保存是一种反模式,虽然“处置模式”是一种处理有这种情况的聪明方法,但这只是让它成为处理你做了一些愚蠢的事实的聪明方法。任何人都应该使用它的唯一时间是当它通过继承某些东西而强加于他们时。
    【解决方案2】:

    以下是主要事实

    1) Object.Finalize 是您的类在具有终结器时覆盖的内容。 ~TypeName() 析构函数方法只是 'override Finalize()' 等的简写

    2) 如果您在完成之前在 Dispose 方法中处理资源(即从 using 块中出来时等),则调用 GC.SuppressFinalize。如果您没有终结器,则无需执行此操作。如果你有一个终结器,这可以确保对象从终结队列中取出(所以我们不会两次处置东西,因为终结器通常也会调用 Dispose 方法)

    3) 您将终结器实现为“故障安全”机制。终结器保证运行(只要 CLR 没有中止),因此它们允许您确保在未调用 Dispose 方法的情况下清理代码(可能程序员忘记在“使用”中创建实例阻止等。

    4) 终结器是昂贵的,因为具有终结器的类型不能在第 0 代集合中被垃圾收集(最有效),并且通过在 F-Reachable 队列中引用它们被提升到第 1 代,因此它们代表 GC 根。直到 GC 执行第 1 代集合时才会调用终结器并释放资源 - 所以只有在非常重要时才实施终结器 - 并确保需要终结器的对象尽可能小 - 因为所有可以被您的可终结对象到达也将被提升到第 1 代。

    【讨论】:

      【解决方案3】:

      保留第一个版本,更安全,是dispose模式的正确实现。

      1. 调用 SuppressFinalize 告诉 GC 你已经完成了所有自己(你的类持有的资源)的销毁/处置,并且它不需要调用析构函数。

      2. 如果使用您的类的代码已经已经调用了 dispose 并且您不应该告诉 GC 再次 dispose,您需要进行测试。

      参见this MSDN 文档(Dispose 方法应调用 SuppressFinalize)。

      【讨论】:

      • 是的,我明白 SupressFinalze 会阻止 GC 调用 Finalize 。但是我的疑问是,当我没有析构函数时,为什么我们需要调用 SupressFinalze 。因为 Finalize 只会为终结器队列中的那些对象调用,而我的对象没有析构函数,所以任何方式 GC 都不会调用 . 2)我的第二个问题是,为什么 dispose pattens 坚持使用 boolean 的重载 dispose 函数。这将控制托管或非托管资源的释放。当对象要被释放时,为什么我们需要单独处理资源,让我们全部释放。
      • 该规则仅在您需要终结器或您需要允许子类具有终结器时才相关。在许多情况下,情况并非如此。
      • @somaraj:重点是你的类可能没有终结器,但子类可能。
      • @JonSkeet:为什么要禁止子类的终结器? base.Dispose() 是从子类 Dispose() 调用的,所以那里应该已经有一个 GC.SupressFinalize。
      • @adrianm:理想情况下,SupressFinalize() 应该只在 所有 派生类完成其清理逻辑之后调用。唯一知道的是非虚拟包装器。
      【解决方案4】:

      1.回答第一个问题

      基本上,如果您的类没有 finalize 方法(析构函数),您不必调用 SuppressFinalize 方法。我相信即使由于缺乏知识而没有 finalize 方法,人们也会调用 SupressFinalize。

      2。回答第二个问题

      Finalize 方法的目的是释放非托管资源。要理解的最重要的一点是,当对象在终结队列中时,会调用 Finalize 方法。垃圾收集器收集所有可以销毁的对象。垃圾收集器在销毁之前将已经完成的对象添加到最终队列中。还有另一个 .net 后台进程可以为完成队列中的对象调用 finalize 方法。当后台进程执行 finalize 方法时,该特定对象的其他托管引用可能已被销毁。因为在敲定执行时没有特定的顺序。因此,Dispose Pattern 希望确保 finalize 方法不会尝试访问托管对象。这就是为什么托管对象会出现在 finalize 方法无法访问的“if (disposing)”子句中。

      【讨论】:

      【解决方案5】:

      您应该始终调用 SuppressFinalize(),因为您可能拥有(或将来拥有)实现终结器的派生类 - 在这种情况下,您需要它。

      假设您有一个没有终结器的基类 - 您决定不调用 SuppressFinalize()。然后 3 个月后,您添加了一个派生类,该派生类添加了一个终结器。您很可能会忘记进入基类并添加对 SuppressFinalize() 的调用。如果没有终结器,调用它并没有什么坏处。

      我建议的 IDisposable 模式发布在这里:How to properly implement the Dispose Pattern

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-05-16
        • 2018-05-24
        • 1970-01-01
        • 2011-02-04
        • 2013-02-14
        • 1970-01-01
        • 2017-03-22
        • 1970-01-01
        相关资源
        最近更新 更多