提示:关于使用 C++ 进行最终处理的信息,请参考:[Visual C++ 中的析构器与最终清理器]。
类的实例经常使用那些没有通过运行时而被管理的资源(如窗口处理器(HWND)、数据库连接,以及等等)来封装控件。因此,你应该同时提供明确的方式与隐式的方式来释放这些资源。通过为对象(C# 与 C++ 中的析构器语法)而实现被保护的 Finalize 方法来提供隐式的控件。垃圾回收器会在不再有任何有效的对象引用之后调用这个方法。
在有些情况下,你可能需要为程序员提供使用一个能够在垃圾回收器释放对象之前明确地释放这些外部资源的对象。如果外部资源是稀有的或者是昂贵的,又如果程序员在它们不再被使用的时候而明确地释放资源,就能够实现更好的性能。提供明确的控件,并且实现通过 IDisposable 接口而被提供的 Dispose 方法。对象的消费者应该会在对象的使用被完成的时候调用这个方法。即使对象的其他引用是活动的,然而 Dispose 却仍然能够被调用。
注意:即使在你提供了使用 Dispose 的隐式控件的时候,你还应该使用 Finalize 方法来提供明确的清理任务。Finalize 提供一个候补来防止在程序员忘记调用 Dispose 的时候所出现的资源漏洞。
关于实现 Finalize 与 Dispose 来清理非托管资源的更多信息,请参考:[废物收集]。下列代码范例说明了实现 Dispose 的基本设计模式。这个范例需要使用 System 命名空间。
Visual Basic
' 基类的设计模式。 Public Class Base Implements IDisposable ' 优雅地处理多个 Dispose 调用的字段。 Dim disposed as Boolean = false ' 实现 IDisposable。 Public Overloads Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(Me) End Sub Protected Overloads Overridable Sub Dispose(disposing As Boolean) If disposed = False Then If disposing Then ' 释放其他状态(被托管的对象)。 disposed = True End If End If ' 释放你自己的状态(非托管的对象)。 ' 把庞大的字段设置成 null。 End Sub Protected Overrides Sub Finalize() ' 简单地调用 Dispose(False)。 Dispose (False) End Sub End Class ' 派生类的设计模式。 Public Class Derived Inherits Base ' 优雅地处理多个 Dispose 调用的字段。 Dim disposed as Boolean = false Protected Overloads Overrides Sub Dispose(disposing As Boolean) If disposed = False Then If disposing Then ' 释放被托管的资源。 disposed = True End If End If ' 释放非托管的资源。 ' 把庞大的字段设置成 null。 ' 调用基类的 Dispose。 Mybase.Dispose(disposing) End Sub ' 派生类没有无参数的 Finalize 方法或者 Dispose 方法,因为它从基类中继承了这些方法。 End Class
C#
// 基类的设计模式。 public class Base: IDisposable { // 实现 IDisposable。 public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { // 释放其他的状态(被托管的对象)。 } // 释放你自己的状态(非托管的对象)。 // 把庞大的字段设置成 null。 } // 为执行最终清理的代码而使用 C# 析构器的语法。 ~Base() { // 简单地调用 Dispose(false)。 Dispose (false); } } // 派生类的设计模式。 public class Derived: Base { protected override void Dispose(bool disposing) { if (disposing) { // 释放被托管的资源。 } // 释放非托管的资源。 // 把庞大的字段设置成 null。 // 调用基类的 Dispose。 base.Dispose(disposing); } // 派生类中没有无参数的 Finalize 方法或者 Dispose 方法,因为它从基类中继承了这些方法。 }
关于为实现 Finalize 与 Dispose 而说明设计模式的详细代码范例,请参考:[实现 Dispose 方法]。
自定义 Dispose 方法的名称
有时候一个特定的域名称要比 Dispose 更加适用。例如,一个文件的封装可能需要使用名为 Close 的方法。在这种情况下,应该私下地实现 Dispose 方法并且创建一个公共的 Close 方法来调用 Dispose 方法。下列代码范例就说明了这个模式。你可以在你的域中使用一个合适的方法名称来替换 Close 方法。这个范例需要 System 命名空间。
Visual Basic
' 不要让这个方法可重载。 ' 不应该允许派生类来重载这个方法。 Public Sub Close() ' 调用无参数的 Dispose 方法。 Dispose() End Sub
C#
// 不要让这个方法虚拟化。 // 不应该允许派生类来重载这个方法。 public void Close() { // 调用无参数的 Dispose 方法。 Dispose(); }
Finalize 方法
下列规则概述了 Finalize 方法的使用指南:
- 只为需要执行最终清理任务的对象实现 Finalize 方法。因为使用 Finalize 方法是需要付出性能代价的。
- 如果你需要 Finalize 方法,考虑实现 IDisposable 接口来允许类的用户避免付出调用 Finalize 方法时的代价。
- 不要让 Finalize 方法更加地公开化。它应该是被保护的,而不是公共的。
- 一个对象的 Finalize 方法应该释放属于该对象的任何外部资源。而且,一个 Finalize 方法应该只释放保持在该对象之上的资源。另外,Finalize 方法还不应该引用任何其他的对象。
- 不要为不同于对象基类的对象而直接调用 Finalize 方法。这种做法在 C# 编程语言中并不是一个有效的操作。
从对象的 Finalize 方法中调用基类的 Finalize 方法。
提示:基类的 Finalize 方法自动被 C# 与 C++ 析构器的语法而调用。
Dispose 方法
下列规则概述了 Dispose 方法的使用指南:
- 为一个封装了需要明确被释放的资源的类型而实现清理设计模式。因此用户能够通过调用公共的 Dispose 方法来释放外部资源。
- 为一个在派生类型之上保持资源的基本类型而实现清理设计模式。如果基本类型有一个 Close 方法,那么这通常表示是需要实现 Dispose 方法的。在这种情况下,不要为基本类型而实现 Finalize 方法。Finalize 方法应该在任何引入了需要进行清理的资源的派生类型中而被实现。
- 在类型中的 Dispose 方法中释放任何可清理的资源。
- 在实例的 Dispose 方法被调用之后,防止来自于运行中的 Finalize 方法对于 GC.SuppressFinalize 方的调用。基于这个规则的一个罕见的例外情形就是在没有通过 Dispose 方法而被掩饰的 Finalize 方法中而必须被完成的任务。
- 调用基类的 Dispose 方法,如果它实现了 IDisposable 接口。
- 不要假设 Dispose 方法将会被调用。属于类型的非托管资源同样应该在没有调用 Dispose 方法的事件的 Finalize 方法中被释放。
- 在资源已经被清理的时候,从这个类型(除了 Dispose 方法以外)的实例方法中抛出 ObjectDisposedException 异常。但是这个规则并不适用于 Dispose 方法,因为 Dispose 方法应该是在不抛出异常的情况之下能够多次被调用的。
- 通过基类型的层次来传播 Dispose 方法的调用。Dispose 方法应该释放所有通过该类以及通过该对象所拥有的任何对象而被保持的资源。例如,你可以创建一个在 Stream 类与 Encoding 类之上被保持的 TextReader 对象,并且通过 TextReader 对象而被创建的对象都不会为用户所知。而且,Stream 类与 Encoding 类也都能够获取外部资源。所以,在你调用 TextReader 对象的 Dispose 方法时,它应该轮流调用 Stream 类与 Encoding 类的 Dispose 方法,从而释放它们的外部资源。
- 考虑不允许对象在它的 Dispose 方法被调用之后继续可用。重新创建一个已经被清理的对象将是一个难以实现的模式。
- 允许 Dispose 方法在不抛出异常的情况下能够被多次调用。并且在第一次调用这个方法之后就不应该再完成任何任务。