Finalize vs Dispose

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

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

182518 次浏览

Finalize在该对象不再使用时由GC调用。

Dispose只是一个普通的方法,该类的用户可以调用它来释放任何资源。

如果用户忘记调用Dispose,如果类实现了Finalize,那么GC将确保它被调用。

finalizer方法在对象被垃圾收集时被调用,并且您无法保证何时会发生这种情况(您可以强制执行,但这会影响性能)。

另一方面,Dispose方法是由创建你的类的代码调用的,这样你就可以在代码处理完你的对象时清理和释放你获得的任何资源(非托管数据、数据库连接、文件句柄等)。

标准的做法是实现IDisposableDispose,这样你就可以在using语句中使用你的对象。例如using(var foo = new MyObject()) { }。在终结器中,调用Dispose,以防调用代码忘记释放你。

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

作为对象的用户,您总是使用Dispose。Finalize是针对GC的。

作为类的实现者,如果持有应该被释放的托管资源,则实现Dispose。如果您持有本机资源,则同时实现Dispose和Finalize,并且两者都调用释放本机资源的公共方法。这些习语通常通过私有Dispose(bool处理)方法组合,其中Dispose调用true, Finalize调用false。此方法总是释放本机资源,然后检查处理参数,如果为真,则处理托管资源并调用GC.SuppressFinalize。

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

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

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

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

其他的稍微难一点。实例的WaitEventHandles不是这样使用的,因为它们是用于从一个线程到另一个线程的信号。问题就变成了谁应该在这些函数上调用Dispose ?作为一种保障,此类类型实现了Finalize方法,该方法确保在应用程序不再引用实例时释放资源。

完成

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

处理

  • 在每个有终结器的类型上实现IDisposable
  • 确保对象在调用Dispose方法后不可用。换句话说,避免在Dispose方法被调用之后使用对象。
  • 一旦你用完所有的IDisposable类型,就调用Dispose
  • 允许多次调用Dispose而不引发错误。
  • 使用GC.SuppressFinalize方法禁止以后从Dispose方法中调用终结器
  • 避免创造一次性价值类型
  • 避免在Dispose方法中抛出异常

处理/确定模式

  • 微软建议在使用非托管资源时同时实现DisposeFinalize。即使开发人员忽略显式调用Dispose方法,Finalize实现仍然会运行,并且当对象被垃圾收集时,资源仍然会被释放。
  • 清除Finalize方法和Dispose方法中的非托管资源。另外,从Dispose方法中调用任何。net对象的Dispose方法,这些对象是类中的组件(具有非托管资源作为其成员)。

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

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

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

注意,如果你有一个终结器,那么你也应该实现IDisposable接口,以允许用户显式地释放这些资源,比对象被垃圾收集时更快。

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

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

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

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

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

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

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

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

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

在释放资源之后,__ABC0应该调用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;
}
}
}

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

 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使用两个周期来释放资源,而as dispose使用一个周期.

c#中Finalize和Dispose方法的区别。

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

处理方法:当我们从代码中调用它时,我们对它有低级别的控制。我们可以在觉得不可用的时候回收非托管资源。我们可以通过实现IDisposal模式来实现这一点。

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

注意,即使通过Dispose方法提供显式控制,也应该使用Finalize方法提供隐式清理。Finalize提供了一个备份,以防止程序员在调用Dispose失败时永久地泄漏资源。

摘要如下-

    如果类引用了unmanaged,则为该类编写终结器 资源,你要确保那些非托管资源 当该类的实例被垃圾收集时被释放 自动> < /强。请注意,您不能显式地调用对象的Finalizer—当垃圾回收器认为有必要时,它会自动调用。 另一方面,您实现了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);
}
要回答第一部分,你应该提供人们使用的例子 完全相同的类对象的不同方法。 否则就很难(甚至很奇怪)回答。

至于第二个问题,最好先读这个 IDisposable接口的正确使用 哪个宣称

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

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

Dispose和Finalize之间的主要区别是:

Dispose通常由你的代码调用。当您调用它时,资源立即被释放。人们忘记调用这个方法,所以发明了using() {}语句。当你的程序完成{}中的代码执行时,它会自动调用Dispose方法。

你的代码不会调用Finalize。它意味着由垃圾收集器(GC)调用。这意味着在将来的任何时候,只要GC决定这样做,就可以释放资源。当GC工作时,它将通过许多Finalize方法。如果你在这里面有沉重的逻辑,它会使过程变慢。这可能会导致程序的性能问题。所以小心你放进去的东西。

我个人会在Dispose中编写大部分销毁逻辑。希望这能消除困惑。

我今天一直在寻找这个问题的答案。我将在这里分享我的经验。我的答案是基于这个链接,因为它有我所见过的最清楚的解释。

当您的对象访问非托管资源时,您必须手动释放这些资源。这可以通过IDisposable或finalizer来完成,这意味着它们都会释放非托管资源。

经验法则: 实现IDisposable以释放非托管资源,调用方代码必须调用Dispose方法。如果调用者忘记调用Dispose()方法,您仍然可以提供一个方法来释放那些非托管资源。第一个选择是使用安全句柄来包装非托管资源。第二个选项是定义终结器。在这种情况下,建议使用安全手柄

我认为这个链接是这个问题最清楚的答案。我不知道为什么人们在网上对这个问题给出复杂的解释。这让我很困惑,直到我找到了那个联系。