完成与处置
为什么有些人通过Dispose
方法使用Finalize
方法?
在什么情况下你会使用Dispose
方法的Finalize
方法,反之亦然?
其他人已经介绍了Dispose
和Finalize
之间的区别(顺便提一下, Finalize
方法在语言规范中仍然被称为析构函数),所以我只是稍微介绍Finalize
方法Finalize
的场景。
一些types封装一次性资源的方式,易于使用和处理在一个单一的行动。 一般用法通常是这样的。 打开,读取或写入,closures(Dispose)。 它非常适合using
构造。
其他人则更加困难。 WaitEventHandles
实例不像这样使用,因为它们用于从一个线程发送到另一个线程。 那么这个问题就变成了谁应该调用Dispose
像这样的安全types实现了一个Finalize
方法,当应用程序不再引用实例时,确保资源被处理。
当你的对象被垃圾收集,并且你不能保证什么时候会发生(你可以强制它,但是会损害性能),调用finalizer方法。
另一方面, Dispose
方法意味着由创build类的代码调用,以便在代码完成时清理并释放已获取的任何资源(非托pipe数据,数据库连接,文件句柄等)与你的对象。
标准的做法是实现IDisposable
和Dispose
这样你就可以在using
语句中使用你的对象。 比如using(var foo = new MyObject()) { }
。 而在你的终结者,你叫Dispose
,以防万一调用代码忘记处理你。
Finalize是垃圾回收器在回收对象时调用的backstop方法。 Dispose是应用程序调用的“确定性清除”方法,当不再需要时,它们释放有价值的本机资源(窗口句柄,数据库连接等),而不是让它们无限期地保留,直到GC到达对象。
作为对象的用户,您始终使用Dispose。 Finalize适用于GC。
作为一个类的实现者,如果你持有应该被处置的pipe理资源,你实现Dispose。 如果持有本地资源,则实现Dispose和Finalize,并且都调用释放本地资源的通用方法。 这些成语典型地通过一个专用的Dispose(bool disposing)方法结合在一起,Dispose(调用)为true,Finalize调用为false。 此方法总是释放本地资源,然后检查处置参数,如果为true,则configuration受pipe资源并调用GC.SuppressFinalize。
最终确定
- 终结器应该总是被
protected
,而不是public
或private
这样就不能直接调用应用程序的代码,同时也可以调用base.Finalize
方法 - 终结器应该只释放非托pipe资源。
- 框架并不保证在任何给定的实例上都会执行终结器。
- 永远不要在终结器中分配内存或从终结器中调用虚拟方法。
- 避免在终结器中同步并引发未处理的exception。
- 终结器的执行顺序是非确定性的 – 换句话说,不能依赖终结器中仍然可用的另一个对象。
- 不要定义值types的终结器。
- 不要创build空的析构函数。 换句话说,除非你的类需要清理非托pipe资源,否则你不应该明确地定义一个析构函数,如果你定义了一个,它应该做一些工作。 如果稍后您不再需要清理析构函数中的非托pipe资源,请将其完全删除。
部署
- 在每个具有终结器的types上实现
IDisposable
- 确保在调用
Dispose
方法后,对象变得不可用。 换句话说,在调用Dispose
方法之后避免使用对象。 - 调用
Dispose
所有的IDisposable
types后,处理它们 - 允许
Dispose
被多次调用而不会产生错误。 - 禁止稍后使用
GC.SuppressFinalize
方法从Dispose
方法中调用终结器 - 避免创build一次性值types
- 避免抛出
Dispose
方法中的exception
configuration/最终化模式
- Microsoftbuild议您在使用非托pipe资源时同时实施
Dispose
和Finalize
。 即使开发人员忽略显式调用Dispose
方法,Finalize
实现也会运行,并且在对象被垃圾回收时仍然会释放资源。 - 清理
Finalize
方法中的非托pipe资源以及Dispose
方法。 此外,还可以从Dispose
方法中为任何.NET对象调用Dispose
方法,该对象是该类中的组件(具有非托pipe资源作为其成员)。
当这个对象不再被使用时,Finalize被GC调用。
Dispose是这个类的用户可以调用释放任何资源的普通方法。
如果用户忘记调用Dispose,并且如果类已经实现了Finalize,那么GC将确保它被调用。
MCSDauthentication工具包(考试70-483)第193页中有一些关键字:
析构函数≈(几乎等于)base.Finalize()析构函数被转换成Finalize方法的重写版本,该方法执行析构函数的代码,然后调用基类的Finalize方法。 那么它完全不确定性,你不能知道什么时候会被调用,因为取决于GC。
如果一个类不包含托pipe资源而没有非托pipe资源 ,则不需要实现IDisposable或者具有析构函数。
如果类只有pipe理资源 ,它应该实现IDisposable,但它不需要析构函数。 (执行析构函数时,不能确定托pipe对象是否仍然存在,因此无论如何您都不能调用其Dispose方法。
如果该类只有非托pipe资源 ,则需要实现IDisposable,并且在程序不调用Dispose的情况下需要析构函数。
Dispose方法必须安全地运行多次。 你可以通过使用一个variables来跟踪它是否已经运行过。
Dispose方法应该释放托pipe资源和非托pipe资源 。
析构函数应该只释放非托pipe资源 。 (执行析构函数时,不能确定托pipe对象是否仍然存在,因此无论如何您都不能调用其Dispose方法。
释放资源后,析构函数应该调用GC.SuppressFinalize ,这样对象可以跳过终止队列。
具有非托pipe和受pipe资源的类的实现示例:
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); } // 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; // We don't need the destructor because // our resources are already freed. GC.SuppressFinalize(this); } } }
99%的时间,你不应该担心。 :)但是,如果您的对象持有对非托pipe资源(例如窗口句柄,文件句柄)的引用,则需要为托pipe对象提供一种方法来释放这些资源。 Finalize给出了释放资源的隐式控制。 它被垃圾收集器调用。 Dispose是一种对资源释放进行显式控制的方式,可以直接调用。
垃圾收集的主题还有很多,但这是一个开始。
终结器是用于隐式清理 – 只要一个类pipe理绝对必须清理的资源, 就应该使用它,否则就会泄漏句柄/内存等。
正确地实现一个终结器是非常困难的,应尽可能地避免SafeHandle
类(在.Net v2.0及以上版本中可用)现在意味着你很less(如果有的话)需要实现一个终结器。
IDisposable
接口用于显式清理,并且更常用 – 您应该使用它来允许用户在完成使用对象后显式释放或清理资源。
请注意,如果你有一个终结器,那么你也应该实现IDisposable
接口,以允许用户明确地释放这些资源,比对象被垃圾收集时更快。
请参阅DG Update:Dispose,Finalization和Resource Management ,了解我认为的有关终结器和IDisposable
的最佳和最完整的一组build议。
我知道的最好的例子。
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 } }
C#中Finalize和Dispose方法之间的差异。
GC调用finalize方法来回收非托pipe资源(例如文件操作,窗口API,networking连接,数据库连接),但GC调用它时,时间不固定。 它被GC隐含地调用,这意味着我们没有对其进行低级别的控制。
configuration方法:我们从代码中调用它,我们对它有低级的控制。 我们可以回收非托pipe资源,只要我们觉得它不可用。我们可以通过实现IDisposal模式来实现这一点。
类实例通常封装了对不受运行时pipe理的资源(如窗口句柄(HWND),数据库连接等)的控制。 因此,您应该提供明确的和隐含的方式来释放这些资源。 通过在对象上实现受保护的Finalize方法(C#中的析构函数语法和C ++的托pipe扩展)来提供隐式控制。 垃圾收集器在不再有任何有效的对象引用之后的某个时刻调用这个方法。 在某些情况下,您可能希望为程序员提供一个能够在垃圾回收器释放对象之前显式释放这些外部资源的对象。 如果外部资源稀缺或昂贵,如果程序员不再使用时显式释放资源,则可以获得更好的性能。 为了提供明确的控制,实现IDisposable接口提供的Dispose方法。 对象的使用者在使用该对象时应该调用这个方法。 即使对象的其他引用处于活动状态,也可以调用Dispose。
请注意,即使通过Dispose提供显式控制,也应该使用Finalize方法提供隐式清理。 如果程序员调用Dispose失败,Finalize将提供备份以防止资源永久性泄漏。
我们知道configuration和最终化两者都用来释放非托pipe资源。但不同之处在于最终使用两个周期来释放资源,作为configuration使用一个周期。