对象生存期:如何创建和销毁对象 (Visual Basic)
类的实例(即对象)是使用 New 关键字创建的。 在使用新对象之前,通常必须对其执行初始化任务。 常见的初始化任务包括打开文件、连接到数据库以及读取注册表项的值。 Visual Basic 使用名为“构造函数”的过程(可控制初始化的特殊方法)控制新对象的初始化。
当对象离开范围之后,将由公共语言运行时 (CLR) 释放。 Visual Basic 使用名为“析构函数”的过程控制系统资源的释放。 构造函数和析构函数共同支持创建可靠的和可预测的类库。
使用构造函数和析构函数
构造函数和析构函数控制对象的创建和毁坏。 Visual Basic 中的 Sub New 和 Sub Finalize 过程初始化和销毁对象;它们替换 Visual Basic 6.0 及更早版本中使用的 Class_Initialize 和 Class_Terminate 方法。
Sub New
Sub New 构造函数只能在创建类时运行一次。 不能从同一个类或某个派生类中另一构造函数的首行代码以外的任何位置对该构造函数进行显式调用。 此外,Sub New 方法中的代码始终在类中所有其他代码之前运行。 如果没有为类显式定义 Sub New 过程,Visual Basic 2005 及更高版本在运行时将隐式创建一个 Sub New 构造函数。
若要为类创建构造函数,请在类定义的任何位置创建名为 Sub New 的过程。 若要创建参数化构造函数,请像为其他任何过程指定参数那样为 Sub New 指定参数的名称和数据类型,如下面的代码所示:
Sub New(ByVal s As String)
构造函数频繁地重载,如下面的代码所示:
Sub New(ByVal s As String, i As Integer)
当定义从另一个类派生的类时,构造函数的第一行必须是对基类构造函数的调用,除非基类有一个可访问的无参数构造函数。 例如,对包含以上构造函数的基类的调用将为 MyBase.New(s)。 另外,MyBase.New 是可选的,Visual Basic 运行时会隐式调用它。
编写了用于调用父对象构造函数的代码后,您可以将任何附加初始化代码添加到 Sub New 过程。 Sub New 被作为参数化构造函数调用时可接受参数。 这些参数是从调用构造函数的过程(例如 Dim AnObject As New ThisClass(X))中传递的。
Sub Finalize
在释放对象之前,CLR 会为定义 Sub Finalize 过程的对象自动调用 Finalize 方法。 Finalize 方法可以包含刚好在对象销毁前需要执行的代码(如用于关闭文件和保存状态信息的代码)。 执行 Sub Finalize 会有轻微的性能降低,所以应当只在需要显式释放对象时才定义 Sub Finalize 方法。
提示
CLR 中的垃圾回收器不会(而且不能)释放“非托管对象”,即操作系统在 CLR 环境外直接执行的对象。 这是因为不同的非托管对象必须使用不同的方式释放。 该信息不与非托管对象直接关联;它必须在对象的文档中才能找到。 使用非托管对象的类必须以其 Finalize 方法释放这些对象。
Finalize 析构函数是只能从其所属类或派生类调用的受保护方法。 当销毁对象时系统自动调用 Finalize,因此不应该从派生类的 Finalize 实现的外部显式调用 Finalize。
与 Class_Terminate(它在对象设置为空时会立即执行)不同,当对象失去范围时,Visual Basic 通常要隔一段时间才会调用 Finalize 析构函数。 Visual Basic 2005 和更高版本允许另一种析构函数,即 Dispose,可以随时显式调用该析构函数来立即释放资源。
提示
Finalize 析构函数不应引发异常,因为应用程序无法处理这些异常,而且这些异常会导致应用程序终止。
New 和 Finalize 方法在类层次结构中如何工作
每当创建类的实例时,如果该对象中存在名为 New 的过程,则公共语言运行时 (CLR) 便会尝试执行该过程。 New 是一类名为 constructor 的过程,用于在执行对象中的任何其他代码之前初始化新对象。 New 构造函数可用于打开文件、连接到数据库、初始化变量以及处理任何需要在可使用对象前完成的其他任务。
当创建派生类的实例时,基类的 Sub New 构造函数首先执行,然后执行派生类中的构造函数。 这是因为 Sub New 构造函数中的第一行代码使用语法 MyBase.New() 调用类层次结构中该类本身上面相邻类的构造函数。 然后调用该类层次结构中每个类的 Sub New 构造函数,直到到达基类的构造函数。 此时,基类构造函数中的代码执行,接着执行所有派生类中每个构造函数的代码,最后执行最相近派生的类中的代码。
当不再需要某个对象时,CLR 调用该对象的 Finalize 方法,然后释放其内存。 Finalize 方法称为 destructor,因为它执行清理任务,如保存状态信息、关闭文件和与数据库的连接,以及在释放对象前必须完成的其他任务。
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) 方法的重写。
垃圾回收和 Finalize 析构函数
.NET Framework 使用“引用跟踪垃圾回收”系统定期释放未使用的资源。 Visual Basic 6.0 和早期版本使用名为“引用计数”的另一个系统来管理资源。 虽然这两个系统自动执行同样的功能,但还是有一些重要差异。
当系统确定对象不再需要时,CLR 会定期销毁这些对象。 当系统资源短缺时,对象释放会快一些,否则就不那么频繁。 在对象失去范围和 CLR 释放对象这两个时间之间的延迟意味着您不能准确确定对象将在什么时候销毁,这与 Visual Basic 6.0 和早期版本中对象的情况不同。 在此类情况下,称对象具有“非确定生存期”。 在大多数情况下,非确定生存期并不会对您如何编写应用程序产生影响,只要您记住 Finalize 析构函数可能不会在对象失去范围时立即执行即可。
这两个垃圾回收系统之间的另一个差异涉及到 Nothing 的使用。 为利用 Visual Basic 6.0 和早期版本中的引用计数,程序员有时将 Nothing 赋给对象变量以释放这些变量所保存的引用。 如果变量保存的是对对象的最后一个引用,对象的资源将立即被释放。 在 Visual Basic 的更高版本中,尽管可能存在此过程仍然有价值的情况,但执行此赋值操作从不会导致被引用对象立即释放其资源。 若要立即释放资源,请使用对象的 Dispose 方法(如果可用)。 只有当变量生存期相对于垃圾回收器检测孤立对象所需时间较长时,才应当将变量设置为 Nothing。