完成与处置


Answers:


120

其他人已经介绍了Dispose和之间的区别Finalize(顺便说一句,该Finalize方法在语言规范中仍被称为析构函数),因此,我将对该Finalize方法派上用场的情况做些补充。

某些类型以易于使用和一次性处理的方式封装一次性资源。一般用法通常是这样的:打开,读取或写入,关闭(处置)。它非常适合该using构造。

其他人则有点困难。WaitEventHandles实例没有像这样使用,因为它们用于从一个线程向另一个线程发出信号。问题就变成了谁应该调用Dispose这些?作为此类保护措施类型Finalize,可以实现一种方法,该方法可确保在应用程序不再引用该实例时分配资源。


60
我无法理解这个批准的答案。我仍然想知道不同之处。这是什么?
Ismael 2012年

22
@Ismael:Finalize可能有道理的最大情况是,有许多对象对保持资源有兴趣,但没有办法使对资源不再感兴趣的对象能够发现它是否是资源。最后一个。在这种情况下,Finalize通常只会在没有人对该对象感兴趣时才会触发。松散的时间Finalize对于不可替代的资源(例如文件和锁)来说是可怕的,但对于可替代的资源可能没问题。
supercat

13
+1成为超级猫,代表了一个很棒的新词(对我而言)。情况很清楚,但万一我们其他人为防万一,这是维基百科所说的:“可替代性是指商品或商品的属性,其各个单位可以相互替代,例如,甜原油,公司,债券,贵金属或货币。”
乔恩·库姆斯

5
@JonCoombs:没错,尽管值得注意的是,术语“可替代资源”适用于可以自由替代的东西,直到获得在释放或放弃后又可以自由替代。如果系统具有一组锁对象,并且代码获取了与某个实体相关联的对象,那么只要有人认为该锁的引用是为了将该实体与该实体相关联,则该锁不能替换为任何其他。但是,如果所有关心被保护实体的代码都放弃了锁,...
supercat

...然后它将再次变得可以自由替换,直到与其他实体关联为止。
2015年

135

当对象被垃圾回收并且无法保证何时发生时,将调用finalizer方法(可以强制执行,但会影响性能)。

Dispose另一方面,该方法应由创建您的类的代码调用,以便您可以在代码完成后立即清理并释放已获取的任何资源(非托管数据,数据库连接,文件句柄等)。你的对象。

标准做法是实施IDisposableDispose以便您可以在using陈述中使用对象。如using(var foo = new MyObject()) { }。在终结器中,您调用Dispose,以防万一调用代码忘了处置您。


17
您需要在从Finalize实现中调用Dispose时要格外小心-Dispose也可能会释放托管资源,这些资源您不希望从finalizer接触,因为它们可能已经被自己终结了。
itowlson

6
@itowlson:检查是否为null并假设对象可以被处置两次(第二次调用不执行任何操作)应该足够了。
塞缪尔

7
标准的IDisposal模式和Dispose(bool)的隐藏实现(用于处理可选的托管组件的处置)似乎可以解决该问题。
布罗迪

听起来好像没有理由实现析构函数(〜MyClass()方法),而始终实现并调用Dispose()方法。还是我错了?有人可以给我一个例子,什么时候都应该实施?
dpelisek '19

66

Finalize是backstop方法,由垃圾回收器回收对象时调用。Dispose是一种“确定性清除”方法,应用程序调用该方法以在不再需要它们时释放有价值的本机资源(窗口句柄,数据库连接等),而不是无限期地保留它们,直到GC移至该对象为止。

作为对象的用户,您始终使用Dispose。Finalize用于GC。

作为类的实现者,如果您拥有应该处置的托管资源,则可以实施Dispose。如果您拥有本地资源,那么您将同时实现Dispose和Finalize,并且都调用释放该本地资源的通用方法。这些习惯用法通常通过专用的Dispose(bool dispose)方法进行组合,该方法将Dispose的调用结果为true,而Finalize的调用结果为false。此方法始终释放本机资源,然后检查处理参数,如果为true,则处理托管资源并调用GC.SuppressFinalize。



2
长期以来,对于混合使用自我清理(“托管”)资源和非自我清理(“非托管”)资源的类的原始推荐模式。更好的模式是将每个非托管资源分别包装到其自己的托管对象中,该托管对象不包含任何对其清理不必要的强引用。可终结对象拥有直接或间接强引用的所有对象的GC寿命都将延长。封装清除所需的内容将避免延长不需要的内容的GC寿命。
supercat

2
@JCoombs:Dispose很好,正确实施通常很容易。 Finalize是邪恶的,正确实施通常很难。除其他外,由于GC将确保只要存在对该对象的任何引用,就不会“回收”任何对象的身份,因此很容易清理一堆Disposable对象,其中一些对象可能已经被清理了。没问题; 对Dispose已被调用的对象的任何引用将保留对已被调用的对象的引用Dispose
2013年

2
@JCoombs:相比之下,非托管资源通常没有这种保证。如果对象Fred拥有文件句柄#42并将其关闭,则系统可能会将同一编号附加到提供给其他实体的文件句柄中。在这种情况下,文件句柄#42将不会引用Fred的已关闭文件,而会引用该另一实体正在使用的文件;对于Fred再次试图靠近手柄#42将是灾难性的。尝试100%可靠地跟踪是否已释放一个非托管对象是可行的。试图跟踪多个对象要困难得多。
2013年

2
@JCoombs:如果每个非托管资源被放置在其自己的包装对象,它什么都不做,但控制它的生命周期,那么外部的代码不知道资源是否已被释放,但知道它应该是,如果一直没有它已经,可以安全地要求包装对象释放它;包装对象将知道它是否这样做,并且可以执行或忽略该请求。GC保证对包装器的引用将始终是对包装器的有效引用这一事实是非常有用的保证。
2013年

43

完成

  • 终结器应始终为protected,而不为,public否则private不能直接从应用程序的代码中调用该base.Finalize方法,同时,它可以调用该方法
  • 终结器应仅释放非托管资源。
  • 该框架不保证终结器将在任何给定实例上执行。
  • 切勿在终结器中分配内存或从终结器中调用虚拟方法。
  • 避免同步化,并在终结器中引发未处理的异常。
  • 终结器的执行顺序是不确定的,也就是说,您不能依赖终结器中仍然可用的另一个对象。
  • 不要在值类型上定义终结器。
  • 不要创建空的析构函数。换句话说,除非您的类需要清除非托管资源,否则永远不要显式定义一个析构函数,如果您确实定义了一个析构函数,则它应该做一些工作。如果以后不再需要清理析构函数中的非托管资源,则将其完全删除。

处理

  • IDisposable对具有终结器的每种类型实施
  • 调用该Dispose方法后,请确保使对象不可用。换句话说,避免Dispose在调用方法后使用对象。
  • 完成Dispose所有IDisposable类型的调用
  • 允许Dispose多次调用而不会引发错误。
  • Dispose使用GC.SuppressFinalize方法禁止以后从方法内部调用终结器
  • 避免创建一次性价值类型
  • 避免从Dispose方法内部抛出异常

处置/完成模式

  • Microsoft建议您在使用非托管资源时Dispose以及Finalize在使用它们时都实现。Finalize当对象被垃圾回收时,即使开发人员忽略了Dispose显式调用该方法,该实现也将运行并且资源仍将被释放。
  • 清理Finalize方法以及Dispose方法中的非托管资源。另外,从该Dispose方法中为类中具有组件(具有非托管资源作为其成员)的任何.NET对象调用该Dispose方法。

17
我到处都读过同样的答案,但我仍然不明白每个人的目的是什么。我只阅读一个又一个的规则,仅此而已。
Ismael 2012年

@Ismael:作者除了复制和粘贴MSDN中的某些文本外,没有添加任何其他内容。
塔里克

@tarik我已经学过了。当时我问这个的时候,我确实有“承诺”的概念。
Ismael

31

当不再使用此对象时,GC将调用Finalize。

Dispose只是此类用户可以调用以释放任何资源的普通方法。

如果用户忘记了调用Dispose并且该类已实现Finalize,则GC将确保它被调用。


3
有史以来最干净的答案
dariogriffo

19

第MCg认证工具包(考试70-483)第193页有一些关键内容:

destructor≈(几乎等于)base.Finalize(),将析构函数转换为Finalize方法的替代版本,该方法执行析构函数的代码,然后调用基类的Finalize方法。然后它完全不确定,因为依赖于GC,因此您无法知道何时调用。

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

如果该类仅具有托管资源,则应实现IDisposable但不应具有析构函数。(执行析构函数时,不能确保托管对象仍然存在,因此Dispose()无论如何都不能调用它们的方法。)

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

Dispose()该方法必须安全运行多次。您可以通过使用变量来跟踪它是否曾经运行来实现。

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

析构函数应仅释放不受管的资源。执行析构函数时,不能确保托管对象仍然存在,因此无论如何都不能调用它们的Dispose方法。这是通过使用规范protected void Dispose(bool disposing)模式获得的,在规范模式下,仅释放(配置)托管资源disposing == true

释放资源后,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;
        }
    }
}

2
这是一个很好的答案!但是我认为这是错误的:“析构函数应调用GC.SuppressFinalize”。相反,公共Dispose()方法是否不应调用GC.SuppressFinalize?请参阅:docs.microsoft.com/zh-cn/dotnet/api/…调用此方法可防止垃圾收集器调用Object.Finalize(由析构函数覆盖)。
伊娃

7

99%的时间,您都不必担心。:)但是,如果对象包含对非托管资源的引用(例如,窗口句柄,文件句柄),则需要为托管对象提供一种释放这些资源的方法。Finalize提供了对释放资源的隐式控制。它由垃圾收集器调用。处置是一种对资源释放进行明确控制的方法,可以直接调用。

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


5
我敢肯定,超过1%的C#应用​​程序使用数据库:您必须担心IDisposable SQL方面的问题。
塞缪尔

1
另外,如果封装IDisposable,则应实现IDisposable。其中可能涵盖了其他1%。
达伦·克拉克

@Samuel:我看不出数据库与它有什么关系。如果您正在谈论关闭连接,那很好,但这是另一回事。您无需处置对象即可及时关闭连接。
JP Alioto

1
@JP:但是Using(...)模式使处理起来非常简单。
布罗迪

2
同意,但这就是重点。using模式为您隐藏了Dispose的调用。
JP Alioto

6

终结器用于隐式清理-每当类管理绝对必须清理的资源时,都应使用此终结器,否则会泄漏句柄/内存等。

众所周知,正确实现终结器非常困难,应尽可能避免使用- SafeHandle该类(在.Net v2.0及更高版本中可用)现在意味着您很少(如果有)不再需要实现终结器。

IDisposable接口用于显式清理,并且更常用-您应使用此接口允许用户在使用完对象后显式释放或清理资源。

请注意,如果您有终结器,则还应该实现该IDisposable接口,以允许用户比对象被垃圾回收时更快地显式释放这些资源。

有关我认为是有关终结器和的最佳和最完整的建议集,请参见DG更新:处置,完成和资源管理IDisposable


3

摘要是-

  • 如果类的终结器引用了非托管资源,并且要确保在该类的实例被自动垃圾回收时释放这些非托管资源,则可以为其编写终结器 。请注意,您不能显式调用对象的终结器-垃圾回收器会在必要时自动调用该终结器。
  • 另一方面,当您的类引用了非托管资源时,您可以实现IDisposable接口(并因此为类的结果定义Dispose()方法),但又不想等待垃圾收集器启动(可以随时进行-不受程序员控制),并希望在完成后立即释放这些资源。因此,您可以通过调用对象的Dispose()方法来显式释放非托管资源。

另外,另一个区别是-在Dispose()实现中,您还应该释放托管资源,而这不应在Finalizer中完成。这是因为该对象引用的托管资源很可能在准备完成之前已被清理。

对于使用非托管资源的类,最佳做法是同时定义Dispose()方法和Finalizer,以便在开发人员忘记显式释放对象的情况下用作备用。两者都可以使用共享方法来清理托管和非托管资源:-

class ClassWithDisposeAndFinalize : IDisposable
{
    // Used to determine if Dispose() has already been called, so that the finalizer
    // knows if it needs to clean up unmanaged resources.
     private bool disposed = false;

     public void Dispose()
     {
       // Call our shared helper method.
       // Specifying "true" signifies that the object user triggered the cleanup.
          CleanUp(true);

       // Now suppress finalization to make sure that the Finalize method 
       // doesn't attempt to clean up unmanaged resources.
          GC.SuppressFinalize(this);
     }
     private void CleanUp(bool disposing)
     {
        // Be sure we have not already been disposed!
        if (!this.disposed)
        {
             // If disposing equals true i.e. if disposed explicitly, dispose all 
             // managed resources.
            if (disposing)
            {
             // Dispose managed resources.
            }
             // Clean up unmanaged resources here.
        }
        disposed = true;
      }

      // the below is called the destructor or Finalizer
     ~ClassWithDisposeAndFinalize()
     {
        // Call our shared helper method.
        // Specifying "false" signifies that the GC triggered the cleanup.
        CleanUp(false);
     }

2

我知道的最好的例子。

 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
    }
  }

2

C#中的Finalize和Dispose方法之间的区别。

GC调用finalize方法来回收非托管资源(例如文件操作,Windows api,网络连接,数据库连接),但是调用GC的时间不是固定的。它被GC隐式调用,这意味着我们对其没有低级控制。

处置方法:从代码中调用它时,我们对其具有较低级别的控制。我们可以在感觉不可用时回收非托管资源。我们可以通过实现IDisposal模式来实现。


1

类实例通常封装对运行时未管理的资源的控制,例如窗口句柄(HWND),数据库连接等。因此,您应该提供显式和隐式方式来释放这些资源。通过在对象上实现受保护的Finalize方法来提供隐式控制(C#和C ++的托管扩展中的析构函数语法)。在不再有对该对象的任何有效引用之后,垃圾收集器会在某个时候调用此方法。在某些情况下,您可能希望为使用对象的程序员提供在垃圾收集器释放对象之前显式释放这些外部资源的能力。如果外部资源稀少或昂贵,如果程序员在不再使用资源时显式释放资源,则可以实现更好的性能。要提供显式控制,请实现IDisposable接口提供的Dispose方法。使用该对象完成操作后,对象的使用者应调用此方法。即使对该对象的其他引用仍然有效,也可以调用Dispose。

请注意,即使通过Dispose提供显式控制,也应使用Finalize方法提供隐式清理。如果程序员未能调用Dispose,Finalize提供了备份,以防止资源永久泄漏。


1

Dispose和Finalize之间的主要区别在于:

Dispose通常由您的代码调用。调用资源后,资源立即释放。人们忘记了调用该方法,因此using() {}发明了语句。当程序完成内代码的执行后{},它将Dispose自动调用方法。

Finalize没有被您的代码调用。这是由垃圾收集器(GC)调用的。这意味着将来GC可以决定释放资源。当GC完成工作时,它将经历许多Finalize方法。如果您对此有沉重的逻辑,则会使过程变慢。它可能会导致程序性能问题。因此,请注意放入其中的内容。

我个人将在Dispose中编写大部分销毁逻辑。希望这可以消除混乱。


-1

众所周知,处置和最终确定都用于释放非托管资源。但是区别是最终确定使用两个周期来释放资源,而处置则使用一个周期。


处置会立即释放资源。Finalize可能会或可能不会以任何及时性释放资源。
超级猫

1
嗯,他很可能意味着这个“可终结对象需要在回收内存之前由GC检测两次”,请在此处阅读更多信息:ericlippert.com/2015/05/18/…–
aeroson

-4

要在第一部分回答,您应该提供一些示例,其中人们对完全相同的类对象使用不同的方法。否则很难(甚至奇怪)回答。

至于第二个问题,最好先阅读 IDisposable接口的正确使用,该接口 声称

这是你的选择!但是选择“处置”。

换句话说:GC仅知道终结器(如果有的话。也称为Microsoft的析构函数)。一个好的代码将尝试同时清除(finalizer和Dispose)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.