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