组件由其构造函数(在 Visual Basic 中为 Sub New)初始化,由其析构函数(在 Visual Basic 中为 Sub Finalize)销毁。当创建组件的实例时调用组件的构造函数;之后不能再调用此构造函数。在垃圾回收销毁组件并回收其内存之前调用析构函数。
Visual Basic 说明 在 Visual Basic 的早期版本中, Initialize 和 Terminate 事件分别实现构造函数和析构函数的用途。
等待垃圾回收
当垃圾回收确定组件不能再由任何执行代码访问后,公共语言运行库将调用该组件的析构函数。如果对组件的所有引用都已释放,或者对组件的唯一引用由特定的对象保持,而该对象以同样的方式与所有执行代码隔离(例如循环引用的情况),则将发生上述情况。
鉴于用户使用完组件与调用组件的析构函数之间可能有一个延迟,在 .NET 组件的生存期中另外引入了一步:如果组件获取系统资源(如数据库连接或 Windows 系统对象的句柄),则应实现 IDisposable 接口,并提供 Dispose 方法以便组件的用户可以选择何时释放那些资源。
组件的生存期
- 类型初始化:当创建组件的第一个实例时,执行的第一个代码是任何共享的初始化代码。对任何共享成员的引用都将导致执行共享构造函数。这包括任何初始化的共享字段(成员变量)和可能存在的共享构造函数 (Shared Sub New)。在下列代码中,为整个类创建了引用字体。
注意 与 Shared 相对应的 C# 关键字是 static,不要将其与 Visual Basic 中的 Static 关键字混淆。
' Visual Basic
Public Class ADrawing Class ADrawing
Shared ReadOnly ReferenceFont As New Font("TimesNewRoman", 14)
' Shared constructor does not overload other constructors.
Shared Sub New()Sub New()
' There is no call to the base class's shared constructor.
' Insert code to initialize the shared data.
End Sub
End Class
注意 即使未创建组件的实例,类初始化仍然可以发生。例如,具有共享成员函数的 abstract (MustInherit)
类将被初始化,而即使未创建此类的实例,那些函数仍然可由应用程序使用。 - 实例初始化:当创建组件的实例时,将初始化具有初始化代码的数据成员,并执行适当的构造函数重载。下列代码初始化一个私有字段并定义两个构造函数,一个在没有参数时调用,另一个在用户指定了参数时调用。
' Visual Basic
Class AShape Class AShape
Private answer As Integer = 42
Public Sub New()Sub New()
' Call another constructor with default initialization values.
MyClass.New(System.Drawing.Color.Red, 5280, DefinedSizes.Large)
End Sub
Public Overloads Sub New()Sub New(myColor As Color, myLength As Integer, _
Size As DefinedSizes)
' Insert code to initialize the class.
End Sub
' Defines the DefinedSizes enum
Public Enum DefinedSizesEnum DefinedSizes
Large
Small
End Enum
End Class
处置资源:如果组件实现了 IDisposable 接口,它必须提供 Dispose 方法,以便客户在用完组件后调用此方法。注意,任何从 System.ComponentModel.Component 继承的组件都已有 Dispose 的默认实现。可重写此默认实现以提供附加的清理代码。在 Dispose 方法中,组件释放它可能已分配的所有系统资源,释放对其他对象的引用,并使自身呈现为不可用。可能也存在组件适合调用它自己的 Dispose 方法的情况。下列代码处置一个具有 Dispose 方法的依赖对象。 -
' Visual Basic
' Assumes that the class implements IDisposable
Public Sub Dispose() Sub Dispose() Implements IDisposable.Dispose
myWidget.Dispose
myWidget = Nothing
' Insert additional code.
End Sub
// C#
// Assumes that the class implements IDisposable
public void IDisposable.Dispose()
{
mywidget.Dispose();
mywidget = null;
// Dispose of remaining objects.
}
调用 Dispose 后,客户应释放对组件的任何其余的引用,以便垃圾回收可以回收组件的内存。
- 实例析构:如果垃圾回收检测到没有对组件的其余引用,则运行库将调用组件的析构函数(Visual Basic .NET 中的 Finalize)并释放内存。您应该重写基类的 Finalize 方法(对于 Visual Basic .NET)或实现析构函数(对于 Visual C#)以实现您自己的清除代码,但应始终包括对析构函数或基类的 Finalize 方法的调用。
' Visual Basic
Protected Overrides Sub Finalize() Sub Finalize()
m_Gadget = Nothing
m_Gear = Nothing
MyBase.Finalize()
End Sub
// C#
// In C#, a destructor is used instead of a Finalize method.
~ ThisClass()
{
m_gadget = null;
m_gear = null;
// The base class finalizer is called automatically
}
-
何时应实现 Dispose 方法?
如果组件从 System.ComponentModel.Component 继承,系统会提供 Dispose 的默认实现。可重写此实现以提供自定义清理代码。如果通过创建 Icomponent 的自定义实现生成组件,则应实现 IDisposable 以便为组件提供 Dispose 方法。
如果组件分配了系统对象、数据库连接或其他应在用户使用完组件后立即释放的短缺资源,则组件需要 Dispose 方法。
如果组件有对其他对象的引用,而那些对象具有 Dispose 方法,则也应实现 Dispose 方法。
为什么实现 Dispose?
根据系统活动的不同,在用户使用完组件和垃圾回收检测到组件的代码不可访问之间的时间间隔可能不可预知。如果未提供 Dispose 方法,则在这段间隔期内,组件将一直控制其资源。
最坏情况的方案
设想一下使用数据库连接却没有 Dispose 方法的服务器组件。在有大量内存的服务器上,您可能创建并释放组件的许多实例,而不会对可用内存有太大影响。在此情况下,当释放了对组件的引用后,垃圾回收可能在一段时间内不销毁组件。
最后,所有可用的数据库连接可能都被已释放但尚未销毁的组件占用了。即使服务器的内存够用,它也可能无法响应用户请求。