演练:使用 Visual Basic 创作组件
组件以对象的形式提供可重用的代码。 通过创建对象并调用其属性和方法来使用组件代码的应用程序称为“客户端”。 客户端与它使用的组件可能位于同一个程序集中,也可能位于不同的程序集中。
以下各个过程相互关联,因此过程的执行顺序非常重要。
备注
显示的对话框和菜单命令可能会与“帮助”中的描述不同,具体取决于您现用的设置或版本。若要更改设置,请在“工具”菜单上选择“导入和导出设置”。有关更多信息,请参见 Visual Studio 设置。
创建项目
创建 CDemoLib 类库和 CDemo 组件
在**“文件”菜单中,依次选择“新建”和“项目”,以打开“新建项目”对话框。 从 Visual Basic 项目类型列表中选择“类库”项目模板,然后在“名称”**框中输入 CDemoLib。
备注
在新建项目时始终要指定该项目的名称。这样就设置了根命名空间、程序集名称和项目名称,同时确保了默认组件位于正确的命名空间中。
在**“解决方案资源管理器”中,右击“CDemoLib”,然后从快捷菜单中选择“属性”。 注意,“根命名空间”框中包含“CDemoLib”**。
根命名空间用于限定程序集中的组件名。 例如,如果两个程序集提供名为 CDemo 的组件,则可以使用 CDemoLib.CDemo 指定 CDemo 组件。
关闭该对话框。
从**“项目”菜单中选择“添加组件”。 在“添加新项”对话框中,选择“组件类”**,然后在“名称”框中键入 CDemo.vb 。 于是,一个名为 CDemo 的组件便添加到您的类库中。
在**“解决方案资源管理器”中,单击“显示所有文件”按钮。 打开“CDemo.vb”节点以显示“CDemo.Designer.vb”文件。 右击“CDemo.Designer.vb”,然后从快捷菜单中选择“查看代码”**。 代码编辑器打开。
注意紧跟在 Partial Public Class CDemo 下面的 Inherits System.ComponentModel.Component。 本节指定您的类所继承的类。 默认情况下,组件从系统提供的 Component 类继承。 Component 类为组件提供了许多功能,包括使用设计器的功能。
找到 Public Sub New()。 选择整个方法体,然后按 Ctrl-X 从 CDemo.Designer.vb 文件中剪切该方法体。
在**“解决方案资源管理器”中,右击“CDemo.vb”,然后从快捷菜单中选择“查看代码”**。 代码编辑器打开。
将剪切内容粘贴到 CDemo 的类体中。 这样,您不需要设计器的干预就可以对新建项进行处理。
在**“解决方案资源管理器”中,右击“Class1.vb”并选择“删除”**。 这将删除与类库一起提供的默认类,因为本演练中将不使用该类。
在**“文件”菜单中,选择“全部保存”**以保存项目。
添加构造函数和终结程序
构造函数控制组件的初始化方式;Finalize 方法控制组件的销毁方式。 CDemo 类的构造函数和 Finalize 方法中的代码用于维护现有 CDemo 对象个数的实时计数。
添加 CDemo 类的构造函数和终结器的代码
在**“代码编辑器”**中,添加成员变量以保持 CDemo 类实例的运行总计,并为每个实例添加 ID 号。
Public ReadOnly InstanceID As Integer Private Shared NextInstanceID As Integer = 0 Private Shared ClassInstanceCount As Long = 0
由于 InstanceCount 和 NextInstanceID 成员变量已声明为 Shared,因此它们仅存在于类级别上。 所有访问这些成员的 CDemo 实例都将使用相同的内存位置。 当在代码中第一次引用 CDemo 类时,共享成员将被初始化。 这可能是第一次创建 CDemo 对象,也可能是第一次访问其中的一个共享成员。
找到 Public Sub New() 和 Public Sub New(Container As System.ComponentModel.IContainer),即 CDemo 类的默认构造函数。 在 Visual Basic 中,所有构造函数都命名为 New。 组件可以有若干带有不同参数的构造函数,但是它们必须都具有名称 New。
备注
构造函数的访问级别决定了哪些客户端能够创建该类的实例。在 Visual Basic 的早期版本中,对象创建由 Instancing 属性控制;如果您已经使用了 Instancing 属性,可能会发现 Visual Basic 中组件实例化的更改 中的内容有所帮助。
将下面的代码添加到 Sub New(),以在创建新的 CDemo 时递增实例计数并设置实例 ID 号。
备注
始终在 InitializeComponent 调用之后添加代码。此时,任何构成组件都已初始化。
InstanceID = NextInstanceID NextInstanceID += 1 ClassInstanceCount += 1
作为 ReadOnly 成员,InstanceID 只能在构造函数中设置。
备注
熟悉多线程处理的用户会十分正确地指出,分配 InstanceID 和递增 NextInstanceID 应该是原子操作。这一点以及与线程有关的其他问题在 演练:用 Visual Basic 创作简单的多线程组件 中阐述。
在构造函数的结尾后面添加以下方法:
Protected Overrides Sub Finalize() ClassInstanceCount -= 1 End Sub
内存管理器在最后回收被 CDemo 对象占用的内存之前要先调用 Finalize。 Finalize 方法在 Object(.NET 类层次结构中所有引用类型的根)中定义。 通过重写 Finalize,可以在组件从内存中被移除之前先执行清除操作。 但是,正如在本演练的随后部分所看到的,完全有理由更早释放资源。
将属性添加到类中
CDemo 类只有一个属性,它是一个共享属性,使客户端能够确定任何给定时刻内存中的 CDemo 对象数。 可以用类似的方法创建方法。
创建 CDemo 类的属性
将以下属性声明添加到 CDemo 类中,以允许客户端检索 CDemo 的实例数。
Public Shared ReadOnly Property InstanceCount() As Long Get Return ClassInstanceCount End Get End Property
备注
属性声明语法不同于 Visual Basic 早期版本中使用的属性声明语法。有关语法更改的更多信息,请参见 Property Procedure Changes for Visual Basic 6.0 Users。
测试组件
若要测试组件,则需要一个使用该组件的项目。 此项目必须是按下**“运行”**按钮后启动的第一个项目。
添加 CDemoTest 客户端项目作为解决方案的启动项目
在**“文件”菜单中指向“添加”,然后选择“新建项目”以打开“添加新项目”**对话框。
选择**“Windows 应用程序”项目模板,在“名称”框中输入 CDemoTest,然后单击“确定”**。
在**“解决方案资源管理器”中,右击“CDemoTest”,然后单击快捷菜单中的“设为启动项目”**。
为了使用 CDemo 组件,客户端测试项目必须具有对该类库项目的引用。 添加引用后,最好将 Imports 语句添加到测试应用程序中以简化组件的使用。
添加对类库项目的引用
在**“解决方案资源管理器”中,单击“显示所有文件”按钮。 右击紧跟在“CDemoTest”下方的“引用”节点,然后从快捷菜单上选择“添加引用”**。
在**“添加引用”对话框中,选择“项目”**选项卡。
双击**“CDemoLib”**类库项目。 **“CDemoLib”将出现在“CDemoTest”项目的“引用”**节点下面。
在**“解决方案资源管理器”中,右击“Form1.vb”,然后从快捷菜单中选择“查看代码”**。
通过添加对 CDemoLib 的引用,可以使用 CDemo 组件的完全限定名,即 CDemoLib.CDemo。
添加 Imports 语句
将下面的 Imports 语句添加到 Form1 的**“代码编辑器”**的顶端,在 Class 声明之上:
Imports CDemoLib
添加 Imports 语句使您得以省略库名,并通过 CDemo 引用该组件类型。 有关 Imports 语句的更多信息,请参见 Visual Basic 中的命名空间。
现在您将创建并使用测试程序来测试您的组件。
理解对象生存期
CDemoTest 程序将通过创建和释放大量的 CDemo 对象来阐释 .NET Framework 中的对象生存期。
添加创建和释放 CDemo 对象的代码
单击**“Form1.vb[Design]”**返回到设计器。
将一个 Button 和一个 Timer 从**“工具箱”的“所有 Windows 窗体”**选项卡拖到 Form1 的设计图面上。
非可视化的 Timer 组件出现在窗体下方一个单独的设计图面上。
双击 Timer1 的图标以创建 Timer1 组件的 Tick 事件的事件处理方法。 将以下代码放置在事件处理方法中。
Me.Text = "CDemo instances: " & CDemo.InstanceCount
每当计时器走过一个刻度,窗体标题都显示 CDemo 类的当前实例计数。 类名用作共享 InstanceCount 属性的限定符(不必通过创建 CDemo 的实例来访问共享成员)。
单击**“Form1.vb [Design]”**选项卡返回到设计器。
右击 Timer1 并从快捷菜单中选择**“属性”。 在“属性”**窗口中,将 Enabled 属性的值设置为 True。 这样就会在创建窗体之后立即启动计时器。
双击 Form1 上的 Button,为该按钮的 Click 事件创建事件处理方法。 将以下代码放置在事件处理方法中。
Dim cd As CDemo Dim ct As Integer For ct = 1 To 1000 cd = New CDemo Next
此代码看起来可能很陌生。 每创建一个 CDemo 实例时,前一个实例即被释放。 当 For 循环完成时,将只留下一个 CDemo 实例。 当事件处理方法退出时,即便是该实例也将被释放,因为变量 cd 会超出范围。
正如您可能已经猜到的,事实并非完全如此。
运行并调试 CDemoTest 和 CDemo 项目
按 F5 键启动解决方案。
客户端项目将启动,并显示 Form1。 注意,此窗体的标题显示为“CDemo 实例:0”。
单击按钮。 窗体标题应显示为“CDemo 实例:1000”。
等到按钮的 Click 事件处理过程完成时,CDemo 的实例已全部释放。 为什么它们没有被回收呢? 简言之,内存管理器在后台以较低的优先级终结对象。 只有当系统的可用内存较少时,此优先级才会提高。 这种“懒惰”的垃圾回收方案允许快速分配对象。
继续单击该按钮若干次,同时监视标题的变化。 某些时候,实例数会骤然减少。 这意味着内存管理器已经回收了某些对象的内存。
备注
如果已单击了 10 次以上,而 CDemo 实例的数目尚未减少,则可能需要调整代码以使它能够使用更多的内存。关闭窗体返回开发环境,并将 For 循环中的迭代数目增至 10000。然后再次运行项目。
重复步骤 3。 这一次您会看到,在内存管理器终结更多的对象之前,实例数骤然减少的现象更加明显。
实际上,每当您重复步骤 3 时,您都可能能够在内存管理器介入之前分配更多的 CDemo 对象。 这是因为越来越多的 Visual Studio 部分被交换出来,从而为 CDemo 实例留出了更多的空间。
关闭窗体返回开发环境。