对象生存期:如何创建和销毁对象(Visual Basic)

类的实例(对象)是使用 New 关键字创建的。 在使用新对象之前,通常必须先执行初始化任务。 常见的初始化任务包括打开文件、连接到数据库以及读取注册表项的值。 Visual Basic 使用称为 构造函数 的过程控制新对象的初始化(允许控制初始化的特殊方法)。

对象离开范围后,公共语言运行时(CLR)会释放该对象。 Visual Basic 使用称为 析构函数的过程控制系统资源的释放。 构造函数和析构函数共同支持创建可靠且可预测的类库。

使用构造函数和析构函数

构造函数和析构函数控制对象的创建和销毁。 Sub NewSub Finalize 过程在 Visual Basic 中用于初始化和销毁对象;它们替代了 Visual Basic 6.0 及更低版本中使用的 Class_Initialize 方法和 Class_Terminate 方法。

子新建

Sub New构造函数只能在创建类时运行一次。 除了同一类或派生类的另一行构造函数的第一行代码之外,不能显式调用它。 此外,该方法中的 Sub New 代码始终在类中的任何其他代码之前运行。 如果没有定义类的Sub New方法,Visual Basic 将在运行时隐式创建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)

子终结

在释放对象之前,CLR 会自动调用 Finalize 定义 Sub Finalize 过程的对象的方法。 该方法 Finalize 可以包含需要在销毁对象之前执行的代码,例如用于关闭文件和保存状态信息的代码。 执行 Sub Finalize性能会稍有下降,因此,仅当需要显式释放对象时,才应定义方法 Sub Finalize

注释

CLR 中的垃圾回收器不会(且不能)释放由操作系统直接执行、并在 CLR 环境外部运行的非托管对象。 这是因为必须以不同的方式处理不同的非托管对象。 该信息不直接与非托管对象关联;必须在对象的文档中找到它。 使用非托管对象的类必须在其 Finalize 方法中释放它们。

Finalize析构函数是一种受保护的方法,只能从它所属的类或派生类调用。 系统在销毁对象时自动调用Finalize,因此不应在派生类的Finalize实现之外显式调用Finalize

Class_Terminate一旦对象被设置为“无”立即执行不同,对象失去作用域和Visual Basic调用Finalize析构函数之间通常会有延迟。 Visual Basic .NET 允许第二种类型的析构函数, IDisposable.Dispose可以随时显式调用该函数以立即释放资源。

注释

Finalize析构函数不应引发异常,因为它们不能由应用程序处理,并可能导致应用程序终止。

New 和 Finalize 方法如何在类层级中工作

每当创建类的实例时,公共语言运行时 (CLR) 将尝试执行名为 New 的过程(如果该过程存在于该对象中)。 New 是一种称为constructor的过程,用于在对象执行任何其他代码之前初始化新对象。 New构造函数可用于打开文件、连接到数据库、初始化变量以及处理需要执行的任何其他任务,然后才能使用对象。

创建派生类的实例时, Sub New 基类的构造函数首先执行,然后执行派生类中的构造函数。 发生这种情况是因为构造函数的第一 Sub New 行代码使用语法 MyBase.New()来调用类层次结构中直接上级类的构造函数。 为类层次结构中每个类调用 Sub New 构造函数,直至到达基类的构造函数。 此时,基类的构造函数中的代码将执行,后跟所有派生类中每个构造函数中的代码,最后执行大多数派生类中的代码。

显示类层次结构构造函数和继承的屏幕截图。

不再需要对象时,CLR 会在释放该对象的内存之前调用 Finalize 该方法。 Finalize 方法被称为 destructor,因为它执行清理任务,例如保存状态信息、关闭文件和数据库连接,以及释放对象之前必须完成的其他任务。

屏幕截图显示 Finalize 方法析构函数。

IDisposable 接口

类实例通常控制不由 CLR 管理的资源,例如 Windows 句柄和数据库连接。 必须在类的 Finalize 方法中处理这些资源,以便在垃圾回收器销毁对象时自动释放它们。 但是,仅当 CLR 需要更多可用内存时,垃圾回收器才会销毁对象。 这意味着,在对象超出范围很久之后,资源可能才会被释放。

为了补充垃圾回收,如果你的类实现了IDisposable 接口,它们可以提供一种机制来主动管理系统资源。 IDisposable 有一种方法, Dispose客户端在使用完对象后应调用该方法。 可以使用该方法 Dispose 立即释放资源并执行关闭文件和数据库连接等任务。 与Finalize析构函数不同,Dispose方法不会被自动调用。 想要立即释放资源时,类的客户端必须显式调用 Dispose

正在实现 IDisposable

实现接口的 IDisposable 类应包括以下代码部分:

  • 用于跟踪对象是否已释放的字段:

    Protected disposed As Boolean = False
    
  • 可释放类的资源的 Dispose 的重载。 此方法应由基类的DisposeFinalize方法调用:

    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

派生类不应重写基类 DisposeFinalize 方法。 从派生类的实例中调用这些方法时,这些方法的基类的实现将调用 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

另请参阅