# Visual Basic 中的面向对象编程 II > 原文: [https://zetcode.com/lang/visualbasic/oopii/](https://zetcode.com/lang/visualbasic/oopii/) 在 Visual Basic 教程的这一章中,我们将继续以 Visual Basic 语言描述 OOP。 ## 接口 遥控器是观众和电视之间的接口。 它是此电子设备的接口。 外交礼仪指导外交领域的所有活动。 道路规则是驾车者,骑自行车者和行人必须遵守的规则。 编程中的接口类似于前面的示例。 接口是: * API * 合约 对象通过这些方法与外界交互。 实际的实现对程序员而言并不重要,或者也可能是秘密的。 公司可能会出售图书馆,但它不想透露实际的实现情况。 程序员可能会在 GUI 工具箱的窗口中调用`Maximize`方法,但对如何实现此方法一无所知。 从这个角度来看,接口是一种方法,通过这些方法,对象可以与外界交互,而不会过多地暴露其内部工作原理。 从第二个角度来看,接口就是契约。 如果达成协议,则必须遵循。 它们用于设计应用的架构。 他们帮助组织代码。 接口是完全抽象的类型。 它们使用`Interface`关键字声明。 接口只能具有方法签名和常量。 接口中声明的所有方法签名必须是公共的。 他们不能具有完全实现的方法,也不能具有成员字段。 Visual Basic 类可以实现任何数量的接口。 一个接口还可以扩展任何数量的接口。 实现接口的类必须实现接口的所有方法签名。 接口用于模拟多重继承。 Visual Basic 类只能从一个类继承。 Visual Basic 类可以实现多个接口。 使用接口的多重继承与继承方法和变量无关。 它是关于继承想法或合同的,这些想法或合同由接口描述。 接口和抽象类之间有一个重要的区别。 抽象类为继承层次结构中相关的类提供部分实现。 另一方面,可以通过彼此不相关的类来实现接口。 例如,我们有两个按钮。 经典按钮和圆形按钮。 两者都继承自抽象按钮类,该类为所有按钮提供了一些通用功能。 实现类是相关的,因为它们都是按钮。 另一个示例可能具有类`Database`和`SignIn`。 它们彼此无关。 我们可以应用`ILoggable`接口,该接口将迫使他们创建执行日志记录的方法。 ```vb Option Strict On Module Example Interface IInfo Sub DoInform() End Interface Class Some Implements IInfo Sub DoInform() Implements IInfo.DoInform Console.WriteLine("This is Some Class") End Sub End Class Sub Main() Dim sm As New Some sm.DoInform() End Sub End Module ``` 这是一个演示接口的简单 Visual Basic 程序。 ```vb Interface IInfo Sub DoInform() End Interface ``` 这是接口`IInfo`。 它具有`DoInform()`方法签名。 ```vb Class Some Implements IInfo ``` 我们使用`Implements`从接口实现。 ```vb Sub DoInform() Implements IInfo.DoInform Console.WriteLine("This is Some Class") End Sub ``` 该类提供了`DoInform()`方法的实现。 `Implements`关键字明确指定了我们正在实现的方法签名。 下一个示例显示了一个类如何实现多个接口。 ```vb Option Strict On Module Example Interface Device Sub SwitchOn() Sub SwitchOff() End Interface Interface Volume Sub VolumeUp() Sub VolumeDown() End Interface Interface Pluggable Sub PlugIn() Sub PlugOff() End Interface Class CellPhone Implements Device, Volume, Pluggable Public Sub SwitchOn() Implements Device.SwitchOn Console.WriteLine("Switching on") End Sub Public Sub SwitchOff() Implements Device.SwitchOff Console.WriteLine("Switching on") End Sub Public Sub VolumeUp() Implements Volume.VolumeUp Console.WriteLine("Volume up") End Sub Public Sub VolumeDown() Implements Volume.VolumeDown Console.WriteLine("Volume down") End Sub Public Sub PlugIn() Implements Pluggable.PlugIn Console.WriteLine("Plugging In") End Sub Public Sub PlugOff() Implements Pluggable.PlugOff Console.WriteLine("Plugging Off") End Sub End Class Sub Main() Dim o As New CellPhone o.SwitchOn() o.VolumeUp() o.PlugIn() End Sub End Module ``` 我们有一个`CellPhone`类,它从三个接口继承。 ```vb Class CellPhone Implements Device, Volume, Pluggable ``` 该类实现所有三个接口,并用逗号分隔。 `CellPhone`类必须实现来自所有三个接口的所有方法签名。 ```vb $ ./interface.exe Switching on Volume up Plugging In ``` 运行程序。 下一个示例显示接口如何从多个其他接口继承。 ```vb Option Strict On Module Example Interface IInfo Sub DoInform() End Interface Interface IVersion Sub GetVersion() End Interface Interface ILog Inherits IInfo, IVersion Sub DoLog End Interface Class DBConnect Implements ILog Public Sub DoInform() Implements IInfo.DoInform Console.WriteLine("This is DBConnect class") End Sub Public Sub GetVersion() Implements IVersion.GetVersion Console.WriteLine("Version 1.02") End Sub Public Sub DoLog() Implements ILog.DoLog Console.WriteLine("Logging") End Sub Public Sub Connect() Console.WriteLine("Connecting to the database") End Sub End Class Sub Main() Dim db As New DBConnect db.DoInform() db.GetVersion() db.DoLog() db.Connect() End Sub End Module ``` 我们定义了三个接口。 我们可以按层次组织接口。 ```vb Interface ILog Inherits IInfo, IVersion ``` `ILog`接口继承自其他两个接口。 ```vb Public Sub DoInform() Implements IInfo.DoInform Console.WriteLine("This is DBConnect class") End Sub ``` `DBConnect`类实现`DoInform()`方法。 该方法由该类实现的`ILog`接口继承。 ```vb $ ./interface2.exe This is DBConnect class Version 1.02 Logging Connecting to the database ``` 输出。 ## 多态 多态是以不同方式将运算符或函数用于不同数据输入的过程。 实际上,多态意味着如果类`B`从类`A`继承,则不必继承关于类`A`的所有内容; 它可以完成`A`类所做的某些事情。 (维基百科) 通常,多态是以不同形式出现的能力。 从技术上讲,它是重新定义派生类的方法的能力。 多态与将特定实现应用于接口或更通用的基类有关。 多态是重新定义派生类的方法的能力。 ```vb Option Strict On Module Example MustInherit Class Shape Protected x As Integer Protected y As Integer Public MustOverride Function Area() As Integer End Class Class Rectangle Inherits Shape Sub New(ByVal x As Integer, ByVal y As Integer) Me.x = x Me.y = y End Sub Public Overrides Function Area() As Integer Return Me.x * Me.y End Function End Class Class Square Inherits Shape Sub New(ByVal x As Integer) Me.x = x End Sub Public Overrides Function Area() As Integer Return Me.x * Me.x End Function End Class Sub Main() Dim shapes() As Shape = { New Square(5), _ New Rectangle(9, 4), New Square(12) } For Each shape As Shape In shapes Console.WriteLine(shape.Area()) Next End Sub End Module ``` 在上面的程序中,我们有一个抽象的`Shape`类。 该类变形为两个后代类,即`Rectangle`和`Square`。 两者都提供了自己的`Area()`方法的实现。 多态为 OOP 系统带来了灵活性和可伸缩性。 ```vb Public Overrides Function Area() As Integer Return Me.x * Me.y End Function ... Public Overrides Function Area() As Integer Return Me.x * Me.x End Function ``` `Rectangle`和`Square`类具有`Area`方法的自己的实现。 ```vb Dim shapes() As Shape = { New Square(5), _ New Rectangle(9, 4), New Square(12) } ``` 我们创建三个形状的数组。 ```vb For Each shape As Shape In shapes Console.WriteLine(shape.Area()) Next ``` 我们遍历每个形状并在其上调用`Area`方法。 编译器为每种形状调用正确的方法。 这就是多态的本质。 ## `NotOverridable`,`NotInheritable` `NotOverridable`方法不能被覆盖,`NotInheritable`类不能从中继承。 这些关键字与应用设计有关。 我们不应从某些类继承,并且不应重写某些方法。 ```vb Option Strict On Module Example Class Base Public NotOverridable Sub Say() Console.WriteLine("Base class") End Sub End Class Class Derived Inherits Base Public Overrides Sub Say() Console.WriteLine("Derived class") End Sub End Class Sub Main() Dim o As Base = New Derived o.Say() End Sub End Module ``` 该程序将无法编译。 我们收到错误消息“`Public Overrides Sub Say()`”无法覆盖“`Public NotOverridable Sub Say()`”,因为它被声明为“`NotOverridable`”。 ```vb Option Strict On Module Example NotInheritable Class Math Public Shared Function getPI() As Single Return 3.141592 End Function End Class Class DerivedMath Inherits Math Public Sub Say() Console.WriteLine("DerivedMath class") End Sub End Class Sub Main() Dim o As DerivedMath = New DerivedMath o.Say() End Sub End Module ``` 在上面的程序中,我们有一个原型基础`Math`类。 该类的唯一目的是为程序员提供一些有用的方法和常量。 (出于简单起见,在我们的案例中,我们只有一种方法。)它不是从继承而创建的。 为了防止不知情的其他程序员从此类中派生,创建者创建了`NotInheritable`类。 如果尝试编译该程序,则会出现以下错误:'DerivedMath'无法从类'Math'继承,因为'Math'被声明为'NotInheritable'。 ## 深拷贝与浅拷贝 数据复制是编程中的重要任务。 对象是 OOP 中的复合数据类型。 对象中的成员字段可以按值或按引用存储。 可以以两种方式执行复制。 浅表副本将所有值和引用复制到新实例中。 引用所指向的数据不会被复制; 仅指针被复制。 新的引用指向原始对象。 对引用成员的任何更改都会影响两个对象。 深层副本将所有值复制到新实例中。 如果成员存储为引用,则深层副本将对正在引用的数据执行深层副本。 创建一个引用对象的新副本。 并存储指向新创建对象的指针。 对这些引用对象的任何更改都不会影响该对象的其他副本。 深拷贝是完全复制的对象。 如果成员字段是值类型,则将对该字段进行逐位复制。 如果该字段是引用类型,则复制引用,但不是复制引用的对象。 因此,原始对象中的引用和克隆对象中的引用指向同一对象。 (来自 programmingcorner.blogspot.com 的明确解释) 接下来的两个示例将对对象执行浅表复制和深表复制。 ```vb Option Strict On Module Example Class Color Public red as Byte Public green as Byte Public blue as Byte Sub New(red As Byte, green As Byte, _ blue As Byte) Me.red = red Me.green = green Me.blue = blue End Sub End Class Class MyObject Implements ICloneable Public Id As Integer Public Size As String Public Col As Color Sub New(Id As Integer, Size As String, _ Col As Color) Me.Id = Id Me.Size = Size Me.Col = Col End Sub Public Function Clone() As Object _ Implements ICloneable.Clone Return New MyObject(Me.Id, Me.Size, Me.Col) End Function Public Overrides Function ToString() As String Dim s As String s = String.Format("Id: {0}, Size: {1}, Color:({2}, {3}, {4})", _ Me.Id, Me.Size, Me.Col.red, Me.Col.green, Me.Col.blue) Return s End Function End Class Sub Main() Dim col As New Color(23, 42, 223) Dim obj1 As New MyObject(23, "small", col) Dim obj2 As MyObject obj2 = CType(obj1.Clone(), MyObject) obj2.Id += 1 obj2.Size = "big" obj2.Col.red = 255 Console.WriteLine(obj1) Console.WriteLine(obj2) End Sub End Module ``` 这是一个浅表副本的示例。 我们定义了两个自定义对象:`MyObject`和`Color`。 `MyObject`对象将具有对`Color`对象的引用。 ```vb Class MyObject Implements ICloneable ``` 我们应该为要克隆的对象实现`ICloneable`接口。 ```vb Public Function Clone() As Object _ Implements ICloneable.Clone Return New MyObject(Me.Id, Me.Size, Me.Col) End Function ``` `ICloneable`接口迫使我们创建`Clone()`方法。 此方法返回具有复制值的新对象。 ```vb Dim col As New Color(23, 42, 223) ``` 我们创建`Color`对象的实例。 ```vb Dim obj1 As New MyObject(23, "small", col) ``` 创建`MyObject`对象的实例。 它将`Color`对象的实例传递给其构造函数。 ```vb obj2 = CType(obj1.Clone(), MyObject) ``` 我们创建`obj1`对象的浅表副本,并将其分配给`obj2`变量。 `Clone()`方法返回`Object`,我们期望`MyObject`。 这就是我们进行显式转换的原因。 ```vb obj2.Id += 1 obj2.Size = "big" obj2.Col.red = 255 ``` 在这里,我们修改复制对象的成员字段。 我们增加`Id`,将`Size`更改为`"big"`,然后更改颜色对象的红色部分。 ```vb Console.WriteLine(obj1) Console.WriteLine(obj2) ``` `Console.WriteLine()`方法调用`obj2`对象的`ToString()`方法,该方法返回对象的字符串表示形式。 ```vb Id: 23, Size: small, Color:(255, 42, 223) Id: 24, Size: big, Color:(255, 42, 223) ``` 我们可以看到 ID 分别为 23 和 24。大小不同。 “小”对“大”。 但是,这两个实例的颜色对象的红色部分相同:255。更改克隆对象的成员值(`Id`,`Size`)不会影响原始对象。 更改引用对象(`Col`)的成员也影响了原始对象。 换句话说,两个对象都引用内存中的同一颜色对象。 要更改此行为,我们接下来将做一个深层复制。 ```vb Option Strict On Module Example Class Color Implements ICloneable Public Red as Byte Public Green as Byte Public Blue as Byte Sub New(Red As Byte, Green As Byte, _ Blue As Byte) Me.Red = Red Me.Green = Green Me.Blue = Blue End Sub Public Function Clone() As Object _ Implements ICloneable.Clone Return New Color(Me.Red, Me.Green, Me.Blue) End Function End Class Class MyObject Implements ICloneable Public Id As Integer Public Size As String Public Col As Color Sub New(Id As Integer, Size As String, _ Col As Color) Me.Id = Id Me.Size = Size Me.Col = Col End Sub Public Function Clone() As Object _ Implements ICloneable.Clone Return New MyObject(Me.Id, Me.Size, CType(Me.Col.Clone(), Color)) End Function Public Overrides Function ToString() As String Dim s As String s = String.Format("Id: {0}, Size: {1}, Color:({2}, {3}, {4})", _ Me.Id, Me.Size, Me.Col.Red, Me.Col.Green, Me.Col.Blue) Return s End Function End Class Sub Main() Dim col As New Color(23, 42, 223) Dim obj1 As New MyObject(23, "small", col) Dim obj2 As MyObject obj2 = CType(obj1.Clone(), MyObject) obj2.Id += 1 obj2.Size = "big" obj2.Col.Red = 255 Console.WriteLine(obj1) Console.WriteLine(obj2) End Sub End Module ``` 在此程序中,我们对对象执行深层复制。 ```vb Class Color Implements ICloneable ``` 现在`Color`类实现了`ICloneable`接口。 ```vb Public Function Clone() As Object _ Implements ICloneable.Clone Return New Color(Me.Red, Me.Green, Me.Blue) End Function ``` 我们也为`Color`类提供了`Clone()`方法。 这有助于创建引用对象的副本。 ```vb Public Function Clone() As Object _ Implements ICloneable.Clone Return New MyObject(Me.Id, Me.Size, CType(Me.Col.Clone(), Color)) End Function ``` 现在,当我们克隆`MyObject`时,我们以`Col`引用类型调用`Clone()`方法。 这样,我们也可以获得颜色值的副本。 ```vb $ ./deepcopy.exe Id: 23, Size: small, Color:(23, 42, 223) Id: 24, Size: big, Color:(255, 42, 223) ``` 现在,引用的`Color`对象的红色部分不相同。 原始对象保留了其先前的 23 值。 ## 异常 异常是为处理异常的发生而设计的,这些特殊情况会改变程序执行的正常流程。 引发或引发异常。 在执行应用期间,许多事情可能出错。 磁盘可能已满,我们无法保存文件。 互联网连接可能断开,我们的应用尝试连接到站点。 所有这些都可能导致我们的应用崩溃。 为避免发生这种情况,我们必须应对可能发生的所有可能的错误。 为此,我们可以使用异常处理。 `Try`,`Catch`和`Finally`关键字用于处理异常。 ```vb Option Strict On Module Example Sub Main() Dim x As Integer = 100 Dim y As Integer = 0 Dim z As Double Try z = x \ y Catch e As Exception Console.WriteLine(e.Message) End Try End Sub End Module ``` 在上面的程序中,我们有意将数字除以零。 这会导致错误。 ```vb Try z = x \ y ... End Try ``` 容易出错的语句放在`Try`关键字之后。 ```vb Catch e As Exception Console.WriteLine(e.Message) ... ``` 异常类型跟随`Catch`关键字。 在我们的例子中,我们有一个通用的`Exception`,它将捕获任何类型的异常。 有一些通用的异常,还有一些更具体的异常。 发生错误时,将执行`Catch`关键字后面的语句。 发生异常时,将创建一个异常对象。 从该对象中,我们获得`Message`属性并将其打印到控制台。 当前上下文中任何未捕获的异常都会传播到更高的上下文,并寻找适当的 `catch`块来处理它。 如果找不到任何合适的`catch`块,则 .NET 运行时的默认机制将终止整个程序的执行。 ```vb Option Strict On Module Example Sub Main() Dim z As Double Dim x As Integer = 100 Dim y As Integer = 0 z = x \ y End Sub End Module ``` 在此程序中,我们除以零。 我们没有自定义异常处理。 在 Visual Basic 2008 Express 上,我们收到以下错误消息:“未处理的异常:`System.DivideByZeroException`:试图除以零。”。 ```vb Option Strict On Imports System.IO Module Example Dim fs As FileStream Sub Main() Try fs = File.Open("file", FileMode.OpenOrCreate) Console.WriteLine(fs.Length) Catch e As IOException Console.WriteLine("IO Error") Console.WriteLine(e.Message) Finally Console.WriteLine("Finally") If fs.CanRead = True Then fs.Close() End If End Try End Sub End Module ``` 始终执行`Finally`关键字之后的语句。 它通常用于清理任务,例如关闭文件或清除缓冲区。 ```vb Catch e As IOException Console.WriteLine("IO Error") Console.WriteLine(e.Message) ``` 在这种情况下,我们捕获了特定的`IOException`异常。 ```vb Finally Console.WriteLine("Finally") If fs.CanRead = True Then fs.Close() End If ``` 这些行确保关闭文件处理器。 ```vb Option Strict On Module Example Sub Main() Dim x As Integer Dim y As Integer Dim z As Double Try Console.Write("Enter first number: ") x = Convert.ToInt32(Console.ReadLine()) Console.Write("Enter second number: ") y = Convert.ToInt32(Console.ReadLine()) z = x / y Console.WriteLine("Result: {0:D} / {1:D} = {2:D}", x, y, z) Catch e As DivideByZeroException Console.WriteLine("Cannot divide by zero.") Catch e As FormatException Console.WriteLine("Wrong format of number.") Catch e As Exception Console.WriteLine(e.Message) End Try End Sub End Module ``` 在此示例中,我们捕获了各种异常。 请注意,更具体的异常应先于一般的异常。 我们从控制台读取两个数字,并检查零除错误和数字格式错误。 ```vb $ ./passing.exe Enter first number: et Wrong format of number. ``` 运行示例。 ```vb Option Strict On Module Example Class BigValueException Inherits Exception Sub New(ByVal msg As String) MyBase.New(msg) End Sub End Class Sub Main() Dim x As Integer = 340004 Const LIMIT As Integer = 333 Try If (x > LIMIT) Then Throw New BigValueException("Exceeded the maximum value") End If Catch e As BigValueException Console.WriteLine(e.Message) End Try End Sub End Module ``` 假设我们处于无法处理大量数字的情况。 ```vb Class BigValueException Inherits Exception ``` 我们有一个`BigValueException`类。 该类派生自内置的`Exception`类。 ```vb Dim Const LIMIT As Integer = 333 ``` 大于此常数的数字在我们的程序中被视为“大”。 ```vb Sub New(ByVal msg As String) MyBase.New(msg) End Sub ``` 在构造函数内部,我们称为父级的构造函数。 我们将消息传递给父级。 ```vb If (x > LIMIT) Then Throw New BigValueException("Exceeded the maximum value") End If ``` 如果该值大于限制,则抛出自定义异常。 我们给异常消息“超出最大值”。 ```vb Catch e As BigValueException Console.WriteLine(e.Message) ``` 我们捕获到异常并将其消息打印到控制台。 ## 属性 属性是特殊的类成员。 我们使用预定义的设置和获取方法来访问和修改它们。 属性读取和写入会转换为获取和设置方法调用。 与使用自定义方法调用(例如`object.GetName()`)相比,使用字段符号(例如`object.Name`)访问变量更容易。 但是,就属性而言,我们仍然具有封装和信息隐藏的优势。 ```vb Option Strict On Module Example Class Person Private _name As String Public Property Name() As String Get Return _name End Get Set (Byval Value As String) _name = Value End Set End Property End Class Sub Main() Dim p as New Person p.Name = "Jane" Console.WriteLine(p.Name()) End Sub End Module ``` 我们有一个带有一个属性的简单`Person`类。 ```vb Public Property Name() As String ... End Property ``` 我们使用`Property`关键字在 Visual Basic 中创建属性。 ```vb Get Return _name End Get ``` 我们使用预定义的`Get`关键字为`_name`字段创建访问器方法。 ```vb Set (Byval Value As String) _name = Value End Set ``` 类似地,`Set`关键字为`_name`字段创建一个修改器方法。 ```vb Dim p as New Person p.Name = "Jane" Console.WriteLine(p.Name()) ``` 我们创建`Person`类的实例。 我们使用字段符号访问成员字段。 ```vb $ ./properties.exe Jane ``` 这是该计划的结果。 ## 委托 委托是 .NET Framework 使用的一种类型安全的函数指针。 委托通常用于实现回调和事件监听器。 ```vb Option Strict On Module Example Public Delegate Sub NameDelegate(ByVal msg As String) Class Person Private FirstName As String Private SecondName As String Sub New(First As String, Second As String) Me.FirstName = First Me.SecondName = Second End Sub Public Sub ShowFirstName(msg As String) Console.WriteLine(msg & Me.FirstName) End Sub Public Sub ShowSecondName(msg As String) Console.WriteLine(msg & Me.SecondName) End Sub End Class Sub Main() Dim nDelegate As NameDelegate Dim per As New Person("Fabius", "Maximus") nDelegate = AddressOf per.ShowFirstName nDelegate("Call 1: ") nDelegate = AddressOf per.ShowSecondName nDelegate("Call 2: ") End Sub End Module ``` 在此示例中,我们只有一名委托。 该委托用于指向`Person`类的两个方法。 方法与委托一起调用。 ```vb Public Delegate Sub NameDelegate(ByVal msg As String) ``` 使用`Delegate`关键字创建委托。 委托签名必须与委托调用的方法的签名匹配。 ```vb Dim nDelegate As NameDelegate ``` 在这里,我们创建一个自定义委托类型的变量。 ```vb nDelegate = AddressOf per.ShowFirstName nDelegate("Call 1: ") ``` `AddressOf`运算符用于获取对`ShowFirstName(`方法的引用。 现在我们指向该方法,我们可以通过委托来调用它。 ```vb $ ./simpledelegate.exe Call 1: Fabius Call 2: Maximus ``` 这两个名称都是通过委托打印的。 ## 事件 事件是由某些操作触发的消息。 点击按钮或时钟的滴答声就是这样的动作。 触发事件的对象称为发送者,而接收事件的对象称为接收者。 ```vb Option Strict On Module Example Public Event ValueFive() Dim Random As Integer Public Sub Main() AddHandler ValueFive, AddressOf OnFiveEvent For i As Integer = 0 To 10 Randomize() Random = CInt(Rnd() * 7) Console.WriteLine(Random) If Random = 5 Then RaiseEvent ValueFive() End If Next End Sub Public Sub OnFiveEvent() Console.WriteLine("Five Event occured") End Sub End Module ``` 我们有一个简单的示例,可以在其中创建和启动事件。 生成一个随机数。 如果数字等于 5,则会生成`FiveEvent`事件。 ```vb Public Event ValueFive() ``` 使用`Event`关键字声明事件。 ```vb AddHandler ValueFive, AddressOf OnFiveEvent ``` 在这里,我们将名为`ValueFive()`的事件插入`OnFiveEvent()`子例程。 换句话说,如果触发了`ValueFive`事件,则将执行`OnFiveEvent()`子例程。 ```vb If Random = 5 Then RaiseEvent ValueFive() End If ``` 当随机数等于 5 时,我们引发`ValueFive`事件。 我们使用`RaiseEvent`关键字。 ```vb $ ./event.exe 0 1 5 Five Event occured 2 5 Five Event occured 6 7 6 3 3 1 ``` 该程序的结果可能如下所示。 接下来,我们有一个更复杂的示例。 ```vb Option Strict On Namespace EventSample Public Class FiveEventArgs Inherits EventArgs Public Count As Integer Public Time As Date Public Sub New(ByVal Count As Integer, ByVal Time As Date) Me.Count = Count Me.Time = Time End Sub End Class Public Class Five Private Count As Integer = 0 Public Sub OnFiveEvent(ByVal source As Object, _ ByVal e As FiveEventArgs) Console.WriteLine("Five event {0} occured at {1}", _ e.Count, e.Time) End Sub End Class Public Class RandomGenerator Public Event ValueFive(ByVal source As Object, _ ByVal e As FiveEventArgs) Public Sub Generate() Dim Count As Integer = 0 Dim args As FiveEventArgs For i As Byte = 0 To 10 Dim Random As Integer Randomize() Random = CInt(Rnd * 6) Console.WriteLine(Random) If Random = 5 Then Count += 1 args = New FiveEventArgs(Count, Now) RaiseEvent ValueFive(Me, args) End If Next End Sub End Class Public Class Example Public Shared Sub Main() Dim five As New Five Dim gen As New RandomGenerator AddHandler gen.ValueFive, AddressOf five.OnFiveEvent gen.Generate() End Sub End Class End Namespace ``` 我们有四个类。 `FiveEventArgs`随事件对象一起传送一些数据。 `Five`类封装了事件对象。 `RandomGenerator`类负责生成随机数。 它是事件发送者。 最后是`Example`类,它是主要的应用对象,具有`Main()`方法。 ```vb Public Class FiveEventArgs Inherits EventArgs Public Count As Integer Public Time As Date ... ``` `FiveEventArgs`在事件对象内部传送数据。 它继承自`EventArgs`基类。 `Count`和`Time`成员是将被初始化并随事件携带的数据。 ```vb If Random = 5 Then Count += 1 args = New FiveEventArgs(Count, Now) RaiseEvent ValueFive(Me, args) End If ``` 如果生成的随机数等于 5,我们用当前的`Count`和`Date`值实例化`FiveEventArgs`类。 `Count`变量对生成此事件的次数进行计数。 `Time`值保留事件生成的时间。 使用带有发送者对象和事件参数的`RaiseEvent`关键字发送事件。 ```vb AddHandler gen.ValueFive, AddressOf five.OnFiveEvent ``` 我们将`ValueFive`事件插入其处理器。 ```vb $ ./event2.exe 3 6 5 Five event 1 occured at 9/15/2010 5:06:13 PM 3 5 Five event 2 occured at 9/15/2010 5:06:13 PM 6 3 2 5 Five event 3 occured at 9/15/2010 5:06:13 PM 2 5 Five event 4 occured at 9/15/2010 5:06:13 PM ``` 这是我在计算机上得到的输出。 在 Visual Basic 教程的这一部分中,我们继续讨论 Visual Basic 中的面向对象编程。