31

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

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);
     }
   }       
4

5 回答 5

28

我在这里很危险,但是...大多数人不需要成熟的处置模式。面对直接访问非托管资源(通常是 via 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;
    }
}

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

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

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

请注意,SafeHandle从 .NET 2.0 开始,您需要自己的终结器比在 .NET 1.1 中更罕见。


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

于 2010-04-09T06:35:00.790 回答
6

以下是主要事实

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

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

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

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

于 2010-04-29T11:59:44.810 回答
5

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

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

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

请参阅MSDN 文档(Dispose 方法应调用 SuppressFinalize)。

于 2010-04-09T06:24:35.963 回答
2

1.回答第一个问题

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

2.回答第二个问题

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

于 2013-02-12T00:18:34.117 回答
1

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

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

我建议的 IDisposable 模式发布在这里:如何正确实现 Dispose 模式

于 2016-09-22T19:42:16.143 回答