2024-09-20 10:00:04

Finalize vs Dispose

为什么有些人使用Finalize方法而不是Dispose方法?

在什么情况下你会使用Finalize方法而不是Dispose方法,反之亦然?


当前回答

这是我知道的最好的例子。

 public abstract class DisposableType: IDisposable
  {
    bool disposed = false;

    ~DisposableType()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(false);
      }
    }

    public void Dispose()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(true);
        GC.SuppressFinalize(this);
      }
    }

    public void Close()
    {
      Dispose();
    }

    protected virtual void Dispose(bool disposing)
    {
      if (disposing) 
      {
        // managed objects
      }
      // unmanaged objects and resources
    }
  }

其他回答

其他人已经介绍了Dispose和Finalize之间的区别(顺便说一下,Finalize方法在语言规范中仍然被称为析构函数),所以我只补充一点Finalize方法派上用场的场景。

有些类型以一种易于在单个操作中使用和处理的方式封装一次性资源。一般用法通常是这样的:打开,读或写,关闭(Dispose)。它非常适合使用结构。

其他的稍微难一点。实例的WaitEventHandles不像这样使用,因为它们用于从一个线程发送信号到另一个线程。那么问题就变成了谁应该调用Dispose ?作为一种保护措施,这类类型实现了Finalize方法,该方法确保当应用程序不再引用实例时释放资源。

这是我知道的最好的例子。

 public abstract class DisposableType: IDisposable
  {
    bool disposed = false;

    ~DisposableType()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(false);
      }
    }

    public void Dispose()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(true);
        GC.SuppressFinalize(this);
      }
    }

    public void Close()
    {
      Dispose();
    }

    protected virtual void Dispose(bool disposing)
    {
      if (disposing) 
      {
        // managed objects
      }
      // unmanaged objects and resources
    }
  }

以下是MCSD认证工具包(考试70-483)第193页中的一些关键信息:

析构函数≈(几乎等于)base.Finalize(),析构函数被转换为Finalize方法的重写版本,该方法执行析构函数的代码,然后调用基类的Finalize方法。这是完全不确定的,你不知道什么时候会被调用,因为这取决于GC。

如果一个类不包含托管资源和非托管资源,它就不应该实现IDisposable或具有析构函数。

如果类只有托管资源,它应该实现IDisposable,但不应该有析构函数。(当析构函数执行时,您仍然不能确定托管对象 所以你不能调用它们的Dispose()方法。)

如果类只有非托管资源,则需要实现IDisposable,并需要析构函数以防程序不调用Dispose()。

Dispose()方法必须能够安全地运行多次。您可以通过使用一个变量来跟踪它以前是否运行过来实现这一点。

Dispose()应该同时释放托管和非托管资源。

析构函数应该只释放非托管资源。当析构函数执行时,您 不能确定托管对象是否仍然存在,因此无论如何都不能调用它们的Dispose方法。这是通过使用规范的受保护的void Dispose(bool Dispose)模式获得的,其中只有在Dispose == true时才释放(Dispose)托管资源。

释放资源后,Dispose()应该调用GC。SuppressFinalize,这样对象就可以 跳过结束队列。

一个具有非托管资源和托管资源的类的实现示例:

using System;

class DisposableClass : IDisposable
{
    // A name to keep track of the object.
    public string Name = "";

    // Free managed and unmanaged resources.
    public void Dispose()
    {
        FreeResources(true);

        // We don't need the destructor because
        // our resources are already freed.
        GC.SuppressFinalize(this);
    }

    // Destructor to clean up unmanaged resources
    // but not managed resources.
    ~DisposableClass()
    {
        FreeResources(false);
    }

    // Keep track if whether resources are already freed.
    private bool ResourcesAreFreed = false;

    // Free resources.
    private void FreeResources(bool freeManagedResources)
    {
        Console.WriteLine(Name + ": FreeResources");
        if (!ResourcesAreFreed)
        {
            // Dispose of managed resources if appropriate.
            if (freeManagedResources)
            {
                // Dispose of managed resources here.
                Console.WriteLine(Name + ": Dispose of managed resources");
            }

            // Dispose of unmanaged resources here.
            Console.WriteLine(Name + ": Dispose of unmanaged resources");

            // Remember that we have disposed of resources.
            ResourcesAreFreed = true;
        }
    }
}

99%的情况下,你都不需要担心。但是,如果你的对象持有对非托管资源的引用(例如,窗口句柄,文件句柄),你需要为托管对象提供一种释放这些资源的方法。Finalize对释放资源进行隐式控制。它由垃圾回收器调用。Dispose是一种对资源释放进行显式控制的方法,可以直接调用。

关于垃圾收集的主题还有很多很多需要学习,但这只是一个开始。

终结器用于隐式清理——当一个类管理着绝对必须清理的资源时,你应该使用它,否则你会泄漏句柄/内存等等……

正确实现终结器是出了名的困难,应该尽可能避免——SafeHandle类(在. net v2.0及以上版本中可用)现在意味着您很少(如果有的话)需要实现终结器了。

IDisposable接口用于显式清理,并且使用得更普遍——您应该使用它来允许用户在使用完对象后显式地释放或清理资源。

注意,如果您有终结器,那么您还应该实现IDisposable接口,以允许用户显式地更快地释放这些资源,而不是在对象被垃圾收集的情况下。

请参阅DG更新:处置,终结,和资源管理,我认为是最好的和最完整的一组关于终结器和IDisposable的建议。