为什么有些人使用Finalize
方法而不是Dispose
方法?
在什么情况下你会使用Finalize
方法而不是Dispose
方法,反之亦然?
当您的对象被垃圾收集并且您无法保证何时会发生这种情况时会调用终结器方法(您可以强制它,但它会损害性能)。
Dispose
另一方面,该方法旨在由创建您的类的代码调用,以便您可以在代码完成时清理和释放您获得的任何资源(非托管数据、数据库连接、文件句柄等)你的对象。
标准做法是实现IDisposable
,Dispose
以便您可以在using
声明中使用您的对象。比如using(var foo = new MyObject()) { }
。在你的终结器中,你调用Dispose
,以防调用代码忘记处理你。
Dispose
其他人已经涵盖了和之间的区别Finalize
(顺便说一句,该方法在语言规范中仍称为析构函数),因此我将仅添加一些有关该方法派上用场Finalize
的场景。Finalize
某些类型以一种易于使用和在单个操作中处理它们的方式封装了一次性资源。一般的用法往往是这样的:打开、读取或写入、关闭(Dispose)。它非常适合using
构造。
其他的有点困难。WaitEventHandles
因为实例不是这样使用的,因为它们用于从一个线程向另一个线程发送信号。那么问题就变成了谁应该调用Dispose
这些?作为一种安全措施,这些类型实现了一种Finalize
方法,该方法确保在应用程序不再引用实例时释放资源。
Finalize 是 backstop 方法,由垃圾收集器在回收对象时调用。Dispose 是“确定性清理”方法,由应用程序调用以在不再需要时释放有价值的本机资源(窗口句柄、数据库连接等),而不是让它们无限期地保持直到 GC 轮到对象。
作为对象的用户,您总是使用 Dispose。Finalize 用于 GC。
作为一个类的实现者,如果你持有应该被释放的托管资源,你就实现了 Dispose。如果持有原生资源,则同时实现 Dispose 和 Finalize,并且都调用释放原生资源的通用方法。这些惯用语通常通过私有 Dispose(bool disposing) 方法组合在一起,Dispose 调用为 true,Finalize 调用为 false。此方法始终释放本机资源,然后检查 disposing 参数,如果为 true,则释放托管资源并调用 GC.SuppressFinalize。
当该对象不再使用时,GC 会调用 Finalize。
Dispose 只是一个普通的方法,这个类的用户可以调用它来释放任何资源。
如果用户忘记调用 Dispose 并且该类已实现 Finalize,则 GC 将确保它被调用。
敲定
protected
, notpublic
或者private
不能直接从应用程序代码中调用该方法,同时它可以调用该base.Finalize
方法处置
IDisposable
在每个具有终结器的类型上实现Dispose
确保在调用该方法后使对象不可用。换句话说,避免在Dispose
方法被调用后使用对象。Dispose
所有IDisposable
类型Dispose
多次调用而不会引发错误。Dispose
使用该GC.SuppressFinalize
方法从该方法中抑制以后对终结器的调用Dispose
方法中抛出异常处置/最终模式
Dispose
同时实现两者。当对象被垃圾回收时,即使开发人员忽略了显式调用该方法Finalize
,实现也会运行并且资源仍然会被释放。Finalize
Dispose
Finalize
方法和Dispose
方法中的非托管资源。此外,从该Dispose
方法调用您作为该类内部组件(具有非托管资源作为其成员)的任何 .NET 对象的Dispose
方法。MCSD Certification Toolkit (exam 70-483) page 193 一书中有一些关键点:
析构函数≈(几乎等于)base.Finalize()
,析构函数被转换为Finalize方法的重写版本,执行析构函数的代码,然后调用基类的Finalize方法。然后它完全是不确定的,你无法知道什么时候会被调用,因为取决于 GC。
如果一个类不包含托管资源和非托管资源,则它不应实现IDisposable
或具有析构函数。
如果该类只有 managed resources,它应该实现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;
}
}
}
99% 的时间,你也不应该担心。:) 但是,如果您的对象持有对非托管资源(例如,窗口句柄、文件句柄)的引用,您需要为托管对象提供一种释放这些资源的方法。Finalize 提供对释放资源的隐式控制。它由垃圾收集器调用。Dispose 是一种对资源释放进行显式控制的方法,可以直接调用。
关于垃圾收集的主题还有很多需要了解,但这只是一个开始。
终结器用于隐式清理 - 每当一个类管理绝对必须清理的资源时,您都应该使用它,否则您会泄漏句柄/内存等...
众所周知,正确实现终结器非常困难,应尽可能避免使用SafeHandle
该类(在 .Net v2.0 及更高版本中可用)现在意味着您很少(如果有的话)需要再实现终结器。
该IDisposable
接口用于显式清理并且更常用 - 您应该使用它来允许用户在完成使用对象时显式释放或清理资源。
请注意,如果您有终结器,那么您还应该实现IDisposable
接口以允许用户显式释放这些资源,而不是对象被垃圾回收时。
有关我认为关于终结器和IDisposable
.
C# 中的 Finalize 和 Dispose 方法之间的区别。
GC调用finalize方法回收非托管资源(如文件操作、windows api、网络连接、数据库连接),但GC调用的时间不固定。它被 GC 隐式调用,这意味着我们对它没有低级控制。
Dispose 方法:当我们从代码中调用它时,我们对其进行了低级控制。我们可以在感觉不可用时回收非托管资源。我们可以通过实现 IDisposal 模式来实现这一点。
总结是——
此外,另一个区别是 - 在 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);
}
我知道的最好的例子。
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 的主要区别在于:
Dispose
通常由您的代码调用。当您调用它时,资源会立即释放。人们忘记调用方法,所以using() {}
发明了语句。当你的程序执行完里面的代码后{}
,它会自动调用Dispose
方法。
Finalize
不是由您的代码调用的。它意味着被垃圾收集器(GC)调用。这意味着将来只要 GC 决定释放资源,就可以随时释放资源。当 GC 完成它的工作时,它会经过许多 Finalize 方法。如果你在这方面有很重的逻辑,它会使这个过程变慢。它可能会导致您的程序出现性能问题。所以要小心你放在那里的东西。
我个人会在 Dispose 中编写大部分销毁逻辑。希望这可以消除混乱。
类实例通常封装对不受运行时管理的资源的控制,例如窗口句柄 (HWND)、数据库连接等。因此,您应该提供显式和隐式两种方式来释放这些资源。通过在对象上实现受保护的 Finalize 方法来提供隐式控制(C# 中的析构函数语法和 C++ 的托管扩展)。垃圾收集器在不再有对该对象的任何有效引用后的某个时间点调用此方法。在某些情况下,您可能希望为使用对象的程序员提供在垃圾收集器释放对象之前显式释放这些外部资源的能力。如果外部资源稀缺或昂贵,如果程序员在不再使用资源时显式释放资源,则可以获得更好的性能。要提供显式控制,请实现 IDisposable 接口提供的 Dispose 方法。对象的使用者在使用完对象后应该调用此方法。即使对该对象的其他引用仍然存在,也可以调用 Dispose。
请注意,即使您通过 Dispose 提供显式控制,您也应该使用 Finalize 方法提供隐式清理。Finalize 提供了一个备份,以防止在程序员未能调用 Dispose 时资源永久泄漏。
我今天搜索了很多这个问题的答案。我将在这里分享我的学习。我的回答是基于这个链接,因为它有我见过的最清晰的解释。
当您的对象可以访问非托管资源时,您必须手动释放这些资源。这可以通过 IDisposable 或终结器来完成,这意味着它们都释放非托管资源。
经验法则: 实现 IDisposable 以释放非托管资源,调用者代码必须调用 Dispose 方法。如果调用者忘记调用 Dispose() 方法,您仍然可以提供释放这些非托管资源的方法。第一个选项是使用安全句柄来包装非托管资源。第二个选项是定义终结器。在这种情况下,建议使用安全手柄。
我认为这个链接是这个问题的最清楚的答案。我不知道为什么人们在互联网上对这个问题提供复杂的解释。在我找到那个链接之前,这让我感到困惑。
正如我们所知, dispose 和 finalize 都用于释放非托管资源.. 但不同之处在于 finalize 使用两个周期来释放资源,而 dispose 使用一个周期..
要回答第一部分,您应该提供人们对完全相同的类对象使用不同方法的示例。否则很难(甚至奇怪)回答。
至于第二个问题,最好先阅读这个 Proper use of the IDisposable interface 它声称
这是你的选择!但选择处置。
换句话说:GC 只知道终结器(如果有的话。也称为 Microsoft 的析构函数)。一个好的代码会尝试从两者(终结器和 Dispose)中进行清理。