对象生存期:如何创建和销毁对象
更新:2007 年 11 月
类的实例(即对象)是使用 New 关键字创建的。在使用新对象之前,通常必须对其执行初始化任务。常见的初始化任务包括打开文件、连接到数据库以及读取注册表项的值。Visual Basic 使用名为“构造函数”的过程(可控制初始化的特殊方法)控制新对象的初始化。
当对象离开范围之后,将由公共语言运行时 (CLR) 释放。Visual Basic 使用名为“析构函数”的过程控制系统资源的释放。构造函数和析构函数共同支持创建可靠的和可预测的类库。
Sub New 和 Sub Finalize
Visual Basic 中的 Sub New 和 Sub Finalize 过程初始化和销毁对象;它们替换 Visual Basic 6.0 及更早版本中使用的 Class_Initialize 和 Class_Terminate 方法。与 Class_Initialize 不同,Sub New 构造函数只能在创建类时运行一次。不能从同一个类或某个派生类中另一构造函数的首行代码以外的任何位置对该构造函数进行显式调用。此外,Sub New 方法中的代码始终在类中任何其他代码之前运行。如果没有为类显式定义 Sub New 过程,Visual Basic 2005 及更高版本在运行时将隐式创建一个 Sub New 构造函数。
在释放对象之前,CLR 会为定义 Sub Finalize 过程的对象自动调用 Finalize 方法。Finalize 方法可以包含刚好在对象销毁前需要执行的代码(如用于关闭文件和保存状态信息的代码)。执行 Sub Finalize 会有轻微的性能降低,所以应当只在需要显式释放对象时才定义 Sub Finalize 方法。
说明: |
---|
CLR 中的垃圾回收器不会(而且不能)释放“非托管对象”,即操作系统在 CLR 环境外直接执行的对象。这是因为不同的非托管对象必须使用不同的方式释放。该信息不与非托管对象直接关联;它必须在对象的文档中才能找到。使用非托管对象的类必须以其 Finalize 方法释放这些对象。 |
Finalize 析构函数是只能从其所属类或派生类调用的受保护方法。当销毁对象时系统自动调用 Finalize,因此不应该从派生类的 Finalize 实现的外部显式调用 Finalize。
与 Class_Terminate 不同(当对象设置为“Nothing”后它立即执行),在对象失去范围和 Visual Basic 调用 Finalize 析构函数这两个时间之间通常会有延迟。Visual Basic 2005 及更高版本允许使用另一种类型的析构函数 Dispose,您可以在任何时候显式调用该函数来立即释放资源。
说明: |
---|
Finalize 析构函数不应引发异常,因为应用程序无法处理这些异常,而且这些异常会导致应用程序终止。 |
IDisposable 接口
类实例通常控制那些不由 CLR 管理的资源,如 Windows 句柄和数据库连接。必须使用类的 Finalize 方法释放这些资源,以便当垃圾回收器销毁对象时能够释放这些资源。但是,垃圾回收器只在 CLR 需要更多可用内存时才会销毁对象。这意味着可能直到对象离开范围很久之后才释放资源。
为弥补垃圾回收的不足,如果类实现 IDisposable 接口,则类可以提供一种机制来主动管理系统资源。IDisposable 包含方法 Dispose,当客户端使用完对象时应当调用该方法。可以使用 Dispose 方法立即释放资源并执行如关闭文件和数据库连接之类的任务。与 Finalize 析构函数不同,不会自动调用 Dispose 方法。当需要立即释放资源时,类的客户端必须显式调用 Dispose。
实现 IDisposable
实现 IDisposable 接口的类应当包括下面这些代码段:
用于跟踪是否已释放对象的字段:
Protected disposed As Boolean = False
释放类的资源的 Dispose 的重载。此方法应当由基类的 Dispose 和 Finalize 方法调用:
Protected Overridable Sub Dispose(ByVal disposing As Boolean) If Not Me.disposed Then If disposing Then ' Insert code to free managed resources. End If ' Insert code to free unmanaged resources. End If Me.disposed = True End Sub
只包含以下代码的 Dispose 的实现:
Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(Me) End Sub
只包含以下代码的 Finalize 方法的重写:
Protected Overrides Sub Finalize() Dispose(False) MyBase.Finalize() End Sub
从实现 IDisposable 的类中派生
从实现 IDisposable 接口的基类中派生的类不需要重写任何基方法,除非它使用了需要释放的其他资源。在该情况下,派生类应当重写基类的 Dispose(disposing) 方法以释放该派生类的资源。此重写必须调用基类的 Dispose(disposing) 方法。
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposed Then
If disposing Then
' Insert code to free managed resources.
End If
' Insert code to free unmanaged resources.
End If
MyBase.Dispose(disposing)
End Sub
派生类不应当重写基类的 Dispose 和 Finalize 方法。当从派生类的实例调用这些方法时,基类对这些方法的实现将调用派生类对 Dispose(disposing) 方法的重写。
可视化
下图显示在派生类中继承的方法以及重写的方法。
当按照此 DisposeFinalize 模式操作时,派生类和基类的资源都可正确释放。下图显示释放和终结类时调用的方法。
垃圾回收和 Finalize 析构函数
.NET Framework 使用“参考跟踪垃圾收集”系统定期释放未使用的资源。Visual Basic 6.0 和早期版本使用名为“引用计数”的另一个系统来管理资源。虽然这两个系统自动执行同样的功能,但还是有一些重要差异。
当系统确定对象不再需要时,CLR 会定期销毁这些对象。当系统资源短缺时,对象释放会快一些,否则就不那么频繁。在对象失去范围和 CLR 释放对象这两个时间之间的延迟意味着您不能准确确定对象将在什么时候销毁,这与 Visual Basic 6.0 和早期版本中对象的情况不同。在此类情况下,称对象具有“非确定性生存期”。在大多数情况下,非确定性生存期并不会对您如何编写应用程序产生影响,只要您记住 Finalize 析构函数可能不会在对象失去范围时立即执行即可。
这两个垃圾回收系统之间的另一个差异涉及到 Nothing 的使用。为利用 Visual Basic 6.0 和早期版本中的引用计数,程序员有时将 Nothing 赋给对象变量以释放这些变量所保存的引用。如果变量保存的是对对象的最后一个引用,对象的资源将立即被释放。在 Visual Basic 的更高版本中,尽管可能存在此过程仍然有价值的情况,但执行此赋值操作从不会导致被引用对象立即释放其资源。若要立即释放资源,请使用对象的 Dispose 方法(如果可用)。只有当变量生存期相对于垃圾回收器检测孤立对象所需时间较长时,才应当将变量设置为 Nothing。
请参见
任务
如何:实现 Dispose Finalize 模式 (Visual Basic)