VB
VB学习者的乐园

本章内容:

Visual Basic 2005 引入许多增强功能,旨在使编码更加简单、有效。本章为您介绍其中的 4 种增强功能。泛型为提供一种设计类的方法,这种类能够以类型安全的方式有效地处理不同种类的对象。My 对象提供方便的快捷方式以访问基类库 (Base Class Library) 类,这些类涉及当前运行的应用程序、本地计算机、应用程序的窗体集合、登录的用户以及与该应用程序相关的 Web 服务。通过重载诸如 =(等于)和 >(大于)这样的运算符,现在即可定义比较类的两个实例时产生的结果。最后,将介绍一些新的关键字。

*
本页内容
应用程序:泛型 应用程序:泛型
应用程序:My 对象 应用程序:My 对象
应用程序:运算符 应用程序:运算符
应用程序:关键字 应用程序:关键字

应用程序:泛型

该应用程序阐释了 Microsoft Visual Basic 2005 对泛型的新支持。

新概念

在研究泛型实现之前,值得花点时间分析一下 Visual Basic 2005 添加这种功能的原因。泛型源于这样的需要:以“通用”方式处理任何可能类型的对象。例如,您可能要创建一个可以唯一接受 Employee 对象或 Customer 对象的类,并可通过区分它们以不同方式处理每种对象。

即使没有泛型,Visual Basic 也会提供一些机制来处理对象,而不管对象的类型。例如,使用 Visual Basic 6.0,可在 Collection对象中存储任何类型的对象。这种方法有一些缺点,下一节将对此进行说明。泛型为 Collection 提供一种有效的替代方法,可以强制类型安全,提供更好的性能并与 Microsoft 智能感知技术紧密集成。

Visual Basic 6.0 Collection

Visual Basic 6.0 的确可以让您在 Collection 中存储任何内容。然而,Collection类有很多局限性。例如,假定您想在 Collection 中存储下面的 Employee 类:

‘Visual Basic 6.0
Public SSN As String
Public FirstName As String
Public LastName As String
Public Salary As Currency

在集合中存储这个类相对简单一些:

Dim employees As New Collection
Dim emp As Employee
Set emp = New Employee
emp.SSN = “-11-1111"
emp.FirstName = “Scott"
emp.LastName = “Swigart"
emp.Salary = 50000
employees.Add emp, emp.SSN

此代码首先创建名为 employee 的 Collection实例。然后创建 Employee 对象并用数据填充。最后,将这个 Employee 对象添加到 Collection。以下代码显示之后如何从 Collection 中检索 Employee 对象:

Dim emp2 As Employee
Set emp2 = employees(“-11-1111”)

现在我们来研究 Visual Basic 6.0 Collection 的局限性。首先,您可能希望只在 employees 集合中存储 Employee 对象。然而,无法阻止其他人在 Employees 集合中存储其他类型的对象。另外,当试图从 Employees 集合中检索某一项时,无法知道正在检索什么类型。例如,以下代码就可以编译:

Dim s As String
s = employees(“-11-1111”)

虽然开发人员能够明显地看出这些代码不能正常工作,但编译器不能理解这一点。这将产生一种最糟糕的错误,运行时错误。Collection也限制了智能感知能力。考虑以下代码:

employees(“-11-1111”).LastName = “SomeoneElse”

此代码说明,您可以直接编辑 Collection 中的项。但是,在选择 LastName 属性时,没有任何智能感知支持。此外,从 Visual Basic 的角度来说,Collection可以存储任何内容。对于 Visual Basic 6.0,不能说“创建一个 employee 集合”。

集合类的最后两个局限是性能和灵活性。尽管集合类易于使用,但在用作动态数组时其性能很差。集合类还可像字典一样工作,所以如果您需要更像 Stack 或 Queue 这样的结构,它并不是很适合。

框架集合

.NET Framework 1.0 和 1.1 版仅通过提供更多类型的集合解决集合的一些问题。通过导入 System.Collections命名空间,代码获得对诸如下列集合的访问权限:ArrayList、BitArray、HashTable、Queue、SortedList 和 Stack。这些类型集合的使用情况如表 2-1 所示。

表 2-1 集合类
集合名称 用途

ArrayList

ArrayList适用于创建动态增长的数组。

BitArray

BitArray类被优化用于存储布尔值 (true/false) 数组。

HashTable

HashTable最像 Visual Basic 6 Collection类。该类允许使用键查找值。但是,键和值可为任何类型。

SortedList

SortedList与 HashTable 相似,不同之处在于始终对键进行排序。这意味着如果使用 For…Each 来循环访问该集合,您将始终按排序次序来检索这些项。

Queue

Queue是一种支持“先进先出”模型的集合。

Stack

Stack与 Queue 正好相反,提供“先进后出”功能。

.NET Framework 1.0/1.1 为解决 Visual Basic 6.0 灵活性局限的问题做了很多工作,但这些集合仍然是松散类型的。这意味着可在 ArrayList 中存储任何内容(即使对于给定的应用程序),它使只存储某种特定的类型才有意义。

您真正想要的是这样一种集合,可以在其中指定每个键必须是string,并且每个值必须是 Employee。对于 .NET Framework 1.0/1.1,需要创建自己的类来包装 Hashtable,并提供这种功能。对于 .NET Framework 1.2,可以通过泛型使用更少的代码来解决这个问题。

演练

正如“泛型”应用程序所示,泛型提供严格的类型检查、更强大的智能感知功能,以及更好的性能。换句话说,它们解决了过去集合类遇到的几乎所有的问题。

使用泛型类型

首先,值得一提的是,除了 现有的 .NET Framework 1.0/1.1 集合类外,.NET Framework 1.2 还提供泛型集合。.NET Framework 1.2 决不强制您使用泛型。

如果您确实希望使用新的泛型类型,可通过导入 System.Collections 开始。泛型命名空间。它授予访问 DictionaryListQueue、SortedDictionary 和 Stack 类的权限。使用泛型 Dictionary的示例如 btnConsumeGenerics_Click事件中所示:

Private Sub btnConsumeGenerics_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnConsumeGenerics.Click
Dim employees As New Dictionary(Of String, Employee)
Dim emp As Employee
emp = New Employee
emp.SSN = “-11-1111"
emp.FirstName = “Scott"
emp.LastName = “Swigart"
emp.Salary = 50000
employees.Add(emp.SSN, emp)
txtOutput.Text = “Consuming generics”& vbCrLf & vbCrLf
Dim emp2 As Employee
emp2 = employees.Item(“-11-1111”)
Dim s As String
‘s = employees.Item(“-11-1111”) ‘This is now a syntax error
employees.Item(“-11-1111”).LastName = “SomeoneElse"
txtOutput.Text &= “Employee last name:”& vbCrLf & _
employees.Item(“-11-1111”).LastName
End Sub

如果演练此代码,您将注意到几个有关泛型的有趣事实。首先,将泛型类型如下实例化:

Dim employees As New Dictionary(Of String, Employee)

此代码解释为“在键为字符串 而值为 Employee 的地方创建字典”。注意,使用新的 Of关键字为键指定所需的数据类型。任何存储数值而不是 Employee 对象的尝试都将导致编译时错误。这需要牢记。有了泛型,如果使用错误的类型,那么它是编译时错误,而不是运行时错误。事实上,必须注释掉下面的一行代码,否则应用程序将不运行,因为编译器知道该字典存放的是 Employee 类,而不是字符串:

‘s = employees.Item(“-11-1111”) ‘This is now a syntax error

另外,现在集合中已经有了类型的完整智能感知。如果查看图 2-1,可以看到 Microsoft Visual Studio 知道该字典只保存 Employee 类,并且 Employee 类的属性通过智能感知可用。


2-1 用于泛型集合中项的 IntelliSense。

如您所见,泛型使用简单,它们产生强类型代码(这减少了运行时错误发生的可能性),并使得智能感知具有更强大的功能。这些原因足以说明为什么使用泛型,但是泛型还有许多其他优点,包括性能和代码重用。

.NET Framework 中包括泛型的一个主要原因是性能。简单地说,它们比以前的集合类更快,因为编译器专门针对它们存储的类型进行了优化。在下面的代码中可以看到一个示例,它将数组、ArrayList 和泛型 List 的性能进行了比较:

txtOutput.Text = “Performance”& vbCrLf & vbCrLf
Const iterations As Integer = 5000000
PerfTime.Start()
Dim myArray(iterations) As Integer
For i As Integer = 0 To iterations - 1
myArray(i) = i
Next
Dim elapsed As Integer = PerfTime.Stop
txtOutput.Text &= “Array time: “& elapsed & vbCrLf
myArray = Nothing
GC.Collect()
PerfTime.Start()
Dim myArrayList As New ArrayList
For i As Integer = 0 To iterations - 1
myArrayList.Add(i)
Next
elapsed = PerfTime.Stop
txtOutput.Text &= “ArrayList time: “& elapsed & vbCrLf
myArrayList = Nothing
GC.Collect()
PerfTime.Start()
Dim myList As New List(Of Integer)
For i As Integer = 0 To iterations - 1
myList.Add(i)
Next
elapsed = PerfTime.Stop
txtOutput.Text &= “List time: “& elapsed & vbCrLf
myList = Nothing
GC.Collect()

此段代码将 5 百万个值存储到固定大小的数组中。这些值还存储在一个自动增长的 Array-List 以及同样根据需要增长的泛型 List 中。这些性能数据(在开发此代码的计算机上)可以充分地说明问题:

Array time: 344
ArrayList time: 4656
List time: 797

没有什么比存储特定类型且从不需要调整大小的数组更快。(您的数据可能与所显示的数据不同。)但是,对于一个动态增长的集合类来说,与静态数组相比,其性能相当好。但考察一下 ArrayList。结果不太好。它的速度不及静态数组的十分之一。问题是 ArrayList旨在存储对象。Integer 不是对象;它们是数值类型。在将它们存储到 ArrayList 中之前,它们必须经历一个称为“装箱”的过程,这个过程将 integer 转换为 object。装箱的开销很高,如果存储的是数值类型(integer、DateTime、boolean、您自己的结构等),您将注意到,在使用泛型集合而不是基于Object 的集合时,性能会有重大改进。

有关装箱和拆箱的更多信息,请参阅 MSDN 库中的“Boxing Conversions”和“Unboxing Conversions”。

创建泛型类型和方法

您并不仅限于在 Visual Basic 2005 中使用泛型类型。您还能够创建自己的泛型类型和方法。

泛型方法如果要执行一些不依赖于任何类型的常用算法,则需要创建一个泛型方法。例如,典型的气泡排序法将遍历数组中的所有项,将某一项与下一项比较并根据需要交换数值来对数组进行排序。

如果预先知道只对整数排序,则可将 Swap 方法硬编码为 integer 类型。但是,如果希望能对任何类型进行排序,可以按如下方式编写一个泛型 Swap 方法:

Private Sub Swap(Of ItemType) _
(ByRef v1 As ItemType, ByRef v2 As ItemType)
Dim temp As ItemType
temp = v1
v1 = v2
v2 = temp
End Sub

注意“Of ItemType”。当调用 Swap 方法时,除了标准参数之外,还必须传递数据类型。该数据类型将被替换为 ItemType 的各个实例。下面是一个调用 Swap 的示例:

Swap(Of Integer)(v1, v2)

此代码告知 Swap 方法它们将交换 integer 类型,使 Swap 原始清单中 ItemType 的每个实例都通过 JIT 编译器用 integer 取代。Swap 方法实质上由 JIT 重新编写,如下所示:

Private Sub Swap(ByRef v1 As Integer, ByRef v2 As Integer)
Dim temp As Integer
temp = v1
v1 = v2
v2 = temp
End Sub

这是实际执行的代码。JIT 生成该方法处理 integer 类型的版本。但是,如果以后想对 string 类型进行排序,可按照如下方式再次调用 Swap

Swap(Of String)(v1, v2)

当该方法执行时,JIT 将编写另一个版本的 Swap,这个版本专门用于 string 类型:

Private Sub Swap(ByRef v1 As String, ByRef v2 As String)
Dim temp As String
temp = v1
v1 = v2
v2 = temp
End Sub

使用 Swap 的气泡排序法的完整示例如下所示:

Private Sub btnSortIntegers_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnSortIntegers.Click
Dim ints(9) As Integer
Dim r As New Random
For i As Integer = 0 To 9
ints(i) = r.Next(1, 100)
Next
‘Bubble sort
For j As Integer = 0 To 9
For k As Integer = 9 To 1 Step -1
If ints(k) < ints(k - 1) Then
Swap(Of Integer)(ints(k), ints(k - 1)) End If
Next
Next
txtOutput.Text = “Sort Integers”& vbCrLf & vbCrLf
For i As Integer = 0 To 9
txtOutput.Text &= ints(i) & vbCrLf
Next
End Sub

泛型类型最后,可以创建完整的泛型类。在本例中,类声明使用 Of ItemType,如下所示:

Public Class SomeClass(Of ItemType)
Private internalVar as ItemType
Public Function SomeMethod(ByVal value As ItemType) As ItemType
End Function
End Class

适用于方法的规则也同样适用于类。当该类进行实例化时,JIT 编译器只需用特定的类型来代替 ItemType的各个实例。

约束

泛型还支持称为约束的功能。该功能确保当您指定类型时,它们可以实现最低限度的一些功能。例如,如果实现一种泛型排序算法,则可能希望确保排序的类型实现 Icomparible。可以用一个约束来实现这一点:

Public Class SomeClass(Of ItemType As IComparible)
Public Function SomeMethod(ByVal value As ItemType) As ItemType
End Function
End Class

小结

与基于 Object 的集合类相比,泛型提供了许多优点。首先,泛型类是强类型,这允许在编译时(而不是运行时)可以捕获许多错误。强类型还允许智能感知提供更多的信息。泛型还允许重用代码,创建使用各种类型的泛型算法。最后,泛型集合比基于 Object 的集合速度更快,特别是处理数值类型时。

返回页首返回页首

应用程序:My 对象

Visual Basic 2005 引入了一种快速访问很多重要类的方法,这些类与运行应用程序的计算机、用户、应用程序本身、应用程序的窗体以及任何相关的 Web 服务有关。使用新的 My 对象可以访问所有这些类。

新概念

用 Visual Basic 6 构建应用程序时,您可以访问 Visual Basic 运行库、各种 COM 对象,以及运行的任何一个 Microsoft Windows 操作系统版本的 Win32 API。.NET Framework 的第一个版本将这些功能中的大部分都统一到一个称为基类库的大型类集中。在基类库中,有一些类支持访问有关基础操作系统的信息。有一些类允许您轻松访问有关机器上硬件的信息。还有一些类允许您跨不同应用程序之间的网络轻松通信、加密数据、提供对注册表的访问等。

如果要成为称职的 .NET 开发人员,了解基类库及其提供的丰富功能非常重要。缺乏有关基类库的知识会导致开发人员重复劳动,以及构建已经存在的类。在有些情况下,.NET Framework 的规模使 .NET Framework 提供的功能显得模糊而不明显,以至于很多东西被忽略。已经有大量的文章涉及自制算法以支持只需了解框架类(如 Path或 PasswordDeriveBytes)即可执行的操作。事实上,很多人都会告诉您学习 .NET 不像学习 Visual Basic .NET 或 C# 那样内容繁多,而真正要学的是基类库。

但是,学习基类库也是一个巨大的挑战,因为它太大了。每天使用的类与可能从不需要的其他类混在一起。为了更容易发现 .NET Framework 中的常用类,Visual Basic 2005 现在提供一种称为 My 对象的“快速拨号”。使用 My 对象,您能够方便地访问计算机、应用程序和用户信息,还可以访问 Web 服务和窗体。My 对象只有在编写 Visual Basic .NET 应用程序时才可用,而在使用 C# 时不可直接使用,了解这一点非常重要。

值得注意的重要一点是,My 对象不仅仅是简单的快速拨号。在某些情况下,My 对象中的类提供的功能不限于此:仅通过搜索组成基类库的各种命名空间来实现轻松地查找功能。例如,Folder 对象提供 System.IO.DirectoryInfo类中不可用的其他属性,例如 Drive。My 对象还强制 Microsoft 将计算机、应用程序和用户视为完整的实体,并询问“您能用计算机做些什么?”结果是诸如 My.Computer.Network.Ping 这样的方法。现在您将发现,以前只能通过调用 COM 库或 Win32 API 才可用的功能,现在可以通过 My 对象轻松地访问和发现。

My 对象分成几个焦点领域,详细情况请参见表 2-2。

表 2-2 My 对象模型
对象 描述

My.Application

包含有关运行应用程序的信息,例如标题、工作目录、版本,以及正在使用的公共语言运行库 (CLR) 版本。它还可以访问环境变量,允许您方便地编写本地应用程序日志或自定义日志,等等。

My.Computer

包含有关应用程序运行所在的本地计算机上的基础平台和硬件信息。

My.Forms

当前项目中窗体的所有实例的集合。允许您方便地显示并隐藏窗体,而不必在代码中显式创建窗体的实例。

My.User

包含有关当前用户的信息,包括其显示名称和域名等等。

My.WebServices

允许您方便地访问项目中作为 Web 引用添加的 Web 服务。

正如您所见,My 对象使您在构建应用程序时能够容易并直接地访问每天都要使用的功能。

演练

在本节中,通过查看一个对其进行过深入研究的小示例应用程序,您将很好地理解 My 对象。该应用程序有三个选项卡,如图 2-2 所示,每个选项卡都以 My 对象的特定子部分为重点。


2-2 My.Application 选项卡。

My.Application

带有文本 My.Application的第一个选项卡显示数据网格,它公开作为 My.Application 对象的一部分的很多属性的值。用数据填充该网格的代码如下:

Dim cmds As String = “"
For Each cmd As String In My.Application.CommandLineArgs
cmds &= “, “& cmd
Next
AddnewRow(“Command Line Args", Mid(cmds, 2))
AddnewRow(“Company Name", _
My.Application.AssemblyInfo.CompanyName)
AddnewRow(“Culture", _
My.Application.CurrentCulture.ToString())
AddnewRow(“Description", _
My.Application.AssemblyInfo.Description)
AddnewRow(“Number of Procs", _
My.Application.GetEnvironmentVariable(“NUMBER_OF_PROCESSORS”))
AddnewRow(“Directory Path", _
My.Application.AssemblyInfo.DirectoryPath)
AddnewRow(“Legal Copyright", _
My.Application.AssemblyInfo.LegalCopyright)
AddnewRow(“Event Log Name", _
My.Application.Log.ToString)
AddnewRow(“Product Name", _
My.Application.AssemblyInfo.ProductName)
AddnewRow(“Title", My.Application.AssemblyInfo.Title)
AddnewRow(“Version", _
My.Application.AssemblyInfo.Version.Major & “.”& _
My.Application.AssemblyInfo.Version.Minor & “.”& _
My.Application.AssemblyInfo.Version.Revision & “.”& _
My.Application.AssemblyInfo.Version.Build)

上面的代码故意弄得有些冗长。此代码本可以用反射来循环访问这些属性,但通过在代码中将它们列出来,您可以查看应用程序并更好地了解特定属性返回什么信息。从给定属性检索的单个值可以添加到 DataTable,dt,它随后绑定到 My.Application 选项卡上的 Datagrid。只要访问组成 My.Application对象的属性,就可以收集有关应用程序的大量信息。表 2-3 列出 My.Application对象的属性和方法。

表 2-3 My.Application
属性/方法 描述

ApplicationContext

允许访问与当前线程相关的上下文

AssemblyInfo

允许方便地访问来自 AssemblyInfo.vb 文件的数据,包括 CompanyName、描述、FolderPath、LegalCopyright、LegalTrademark、名称、ProductName、标题和版本

ChangeCurrentCulture

允许更改当前线程运行所在的区域性,这影响诸如字符串操作和格式化等行为

ChangeCurrentUICulture

允许更改当前线程所用的区域性以检索特定于区域性的资源

CommandLineArgs

返回命令行参数的集合

CurrentCulture

返回区域性对象的实例,这使您能够在其他事物之间确定该应用程序的当前区域性

CurrentDirectory

返回应用程序所在的文件夹

CurrentUICulture

返回当前线程正在使用的区域性,以检索特定于区域性的资源

DoEvents

导致处理消息队列中的所有消息

GetEnvironmentVariable

返回本地计算机上的特定环境变量

IsNetworkDeployed

如果应用程序是网络部署的,则返回 True,否则返回 False。

Log

允许编写本地计算机上的应用程序日志

MainForm

一种读写属性,允许设置或获取应用程序将用作其主窗体的窗体

OpenForms

返回表示所有应用程序当前打开窗体的集合

Run

设置并启动 Visual Basic 启动/关闭应用程序模型

SplashScreen

允许设置或获取应用程序的闪屏

开发应用程序时使用 My.Application 的情况

与用 .NET Framework 1.0/1.1、Visual Basic 6 或同时使用两者构建应用程序相比,My.Application对象可以用更少的代码提供更多的功能。在本节中,我们将研究一些可以使用 My.Application 的方法。例如,编写 Event Log 所需的代码现在已缩减为如下两行:

My.Application.Log.WriteEntry(“Application Starting", _
EventLogEntryType.Information, Nothing)

默认情况下,这些代码将一个条目写入调试输出以及程序集的日志文件中。通过在 Visual Studio 中以 Debug 模式运行应用程序并在 Output 窗口中读取输出,或者通过访问日志文件,可以看到此结果。默认情况下,该文件称为 assemblyname.log位于应用程序的数据目录中,可以作为 My.Computer 的属性使用。

Dim AppLogLocation As String = _
My.Computer.FileSystem.SpecialDirectories. _
CurrentUserApplicationData & “\”& _
My.Application.AssemblyInfo.Title & “.log"
MsgBox(“Successfully wrote to log.”& vbCr & _
“Log is located at: “& AppLogLocation)

以前,如果想用 .NET Framework 1.0/1.1 编写 EventLog,需要多行代码才能完成。使用 Visual Basic 6,您可以通过 App 对象获得有限的记录功能,但不能指定事件 ID,并且不能写入 System 或 Security 日志或者创建自己的自定义日志。

另外,My.Application允许您直接访问单行代码中大量的应用程序级信息。可以利用 My.Application中这些功能的一些例子如下:¦ 通过使用 Current-Folder 属性快速确定应用程序所在的文件夹¦ 快速访问程序集元数据,例如 Product Name 和 Company Name 等等。

My.Computer

下一个 My 对象是 Computer 对象。My.Computer对象使您能够访问有关应用程序运行所在的基础平台和硬件的信息。My.Computer 选项卡(如图 2-3 所示)是该示例应用程序中的第二个选项卡,它提取一些可以从 My.Computer对象中检索到的更有趣的属性值。


2-3 My.Computer 选项卡。

用数据填充该网格的代码如下所示:

AddnewRow(“Clock", _
My.Computer.Clock.LocalTime)
AddnewRow(“GMT Time", _
My.Computer.Clock.GmtTime)
AddnewRow(“Tick Count", _
My.Computer.Clock.TickCount)
AddnewRow(“Primary Drive", _
My.Computer.FileSystem.Drives(0).ToString())
AddnewRow(“CRTL Key Down", _
My.Computer.Keyboard.CtrlKeyDown.ToString())
AddnewRow(“Buttons Swapped", _
My.Computer.Mouse.ButtonsSwapped)
AddnewRow(“Mouse Exists", _
My.Computer.Mouse.Exists.ToString())
AddnewRow(“Wheel Scroll Lines", _
My.Computer.Mouse.WheelScrollLines.ToString())
AddnewRow(“Wheel Exists", _
My.Computer.Mouse.WheelExists)
AddnewRow(“Computer Name", _
My.Computer.Name)
AddnewRow(“Connection Status", _
My.Computer.Network.IsAvailable)
AddnewRow(“Device Name", _
My.Computer.Screen.DeviceName)

此代码类似于用来填充应用程序中 My.Application 选项卡上的网格的代码。DataTable,dt 用 My.Computer对象的属性值填充,然后再绑定到网格。

表 2-4 列出 My.Computer对象的属性和方法。

表 2-4 My.Computer
属性/方法 描述

Audio

允许在本地计算机上播放声音文件。

Clipboard

允许访问系统的剪贴板。

Clock

允许访问当前的 GMT 时间、本地时间以及 Tick Count。

FileSystem

允许执行各种输入/输出 (IO) 操作,例如复制文件和目录、移动文件和目录以及读写文件,通常在一行代码中完成。

Info

允许访问有关本地计算机的信息,包括其名称、操作系统、内存和加载的程序集。

Keyboard

允许确定键盘的状态以及键盘上各个键的状态。可以确定 CTRL 键、SHIFT 键或 ALT 键是否按下,CAPS 锁是否处于打开状态,SCROLL LOCK 是否处于打开状态。

Mouse

允许确定所连接鼠标的状态和特定硬件特性,例如按钮数量、是否存在鼠标轮,等等。

Name

为您提供应用程序运行所在的本地计算机的名称。

Network

允许访问本地计算机 IP 地址信息和本地计算机的当前连接状态,并且能够 Ping 地址。

Ports

允许访问本地计算机上的串行端口,以及创建并打开一个新的串行端口对象。

Printers

允许访问本地计算机上的打印机。

Registry

允许方便地访问注册表并能够读写注册表项。

Screen

允许访问连接到系统的所有显示器以及这些显示器的属性,例如 BitsPerPixel 和 WorkingArea 等等。

开发应用程序时使用 My.Computer 的情况

My.Computer对象允许您大量访问可以在各种情况下使用的基础平台。 例如,使用 Network 属性及其相关的 Ping方法,可以方便地 Ping 网络地址。

Dim pingResult As Boolean = _
My.Computer.Network.Ping(“localhost”)
If pingResult = True Then
MessageBox.Show(“Ping of localhost succeeded”)
Else
MessageBox.Show(“Ping of localhost was not successful”)
End If

如果 Ping 成功,则 My.Computer.Network属性的简单 Ping方法将返回 True,否则返回 False。能够 Ping 这样的地址使您可方便地确定应用程序是否能够与特定服务器进行通信。您还可以只使用 My.Computer.Network.IsAvailable 来轻松地确定网络连接状态。IsAvailable属性返回一个 true 或 false 值,这取决于该计算机当前是否有一个网络连接。

应用程序中 My.Computer的另一个不错的用途是在需要访问文件系统时,My.Computer.FileSystem使您可更好地访问文件系统,而使用的代码比 Visual Basic 以前版本中使用的少。借助于 My.Computer.FileSystem,可用一行代码执行下列操作:

¦ 读取文件中的所有文本。¦ 创建完成路径所必需的所有父文件夹时,复制一个文件夹。¦ 创建完成路径所必需的所有父文件夹时,移动一个文件夹。

如果希望从一个文件中读取所有的文本,用下面的代码即可实现:

My.Computer.FileSystem.ReadAllText(filePath)

复制和移动文件夹也变得更加容易:

My.Computer.FileSystem.CopyDirectory(sourcePath, targetPath, True, True)

上面的代码将接受 sourcePath文件夹并将其复制到 targetPath。最后两个 Boolean 参数指定如果发现 targetPath,就将其改写,并且在必要时创建需要用来创建 targetPath的所有父文件夹。用一行代码,您就可以确定 My Documents 文件夹的位置。

MessageBox.Show(My.Computer.FileSystem.SpecialDirectories.MyDocuments)

使用 FileIO对象还可以方便地处理单个文件和文件夹。例如,用一行代码,就可以轻松地对正在使用的文件重命名:

My.Computer.FileSystem.RenameFile(sourceFilePath, targetFilename)

My.User

下一个 My 对象是 My.User对象。My.User对象允许您获得有关当前登录用户的信息,例如其用户名、显示名以及所属的角色。My.User 选项卡如图 2-4 所示。


2-4 My.User 选项卡。

用数据填充该网格的代码如下所示:

AddnewRow(“Identity", My.User.Identity.Name)
AddnewRow(“Is in Administrator Role", _
My.User.IsInRole(“Administrators”).ToString())

用来访问当前用户信息的代码非常简单。需要做的就是访问 User 对象的适当属性。用户对象由表 2-5 所示的属性和方法组成。

开发应用程序时使用 My.User 的情况

My.User对象为您提供大量有关当前登录用户的信息。在许多方面,My.User都是“快速拨号”的一个极好示例,My 对象为开发人员提供该示例以供学习或使用 .NET Framework。

表 2-5 My.User
属性/方法 描述

Identity

返回当前用户的标识

IsInRole

允许您查询该用户是否是特定角色的成员

在 .NET Framework 的以前版本中,如果想访问类似于 My 对象所提供的信息,则必须编写如下代码:

Imports System.Security.Principal
Imports System.Threading.Thread
...
Dim winPrin as WindowsPrincipal = Thread.CurrentPrincipal
MessageBox.Show(winPrin.Identity.Name)

还可以选择编写以下代码:

MessageBox.Show(System.Threading.Thread.CurrentPrincipal.Identity.Name)

但使用 My 对象,可更直观:

My.User.Identity.Name

此代码是 My 对象如何执行 .NET Framework 快速拨号功能的典型示例。第一次接触 .NET Framework 时,大部分开发人员都不会自然地认为,要访问当前登录用户的帐户名,必须访问 Principal 对象或访问来自该应用程序执行线程的当前进程的标识。许多开发人员都期望这些信息在更高的抽象级别上公开。使用 My 对象,可以方便快速地访问这类信息。

My.WebServicesMy.Forms

您将在后面的章节中了解到构成 My 对象的其他两个重要的功能区。它们是 My.WebServices 和 My.Forms。在结束本章这一部分之前,让我们快速浏览一下这些内容。

My.WebServices使您同样可以轻松地访问在访问数据源时所拥有的项目中引用的 Web 服务:

dgOrders.DataSource = _
My.WebServices.Northwind.GetOrders().Tables(“Orders”)

在此代码中可以看到,您不必像在 .NET Framework 1.0/1.1 中那样为 Web 服务创建代理类的实例。在这种情况下,My.WebServices为项目中所有的 Web References 提供一种简单的快速拨号。

最后,但并非最不重要的是,My.Forms使您可以访问包含应用程序窗体的集合。这恢复了一项因 .NET 的出现而临时丢失的 Visual Basic 桌面应用程序开发的重要编程增强功能。如果您以前用 Visual Basic 6 编写过程序,就应该了解可以使用下面的代码来显示项目中的一个窗体:

Form2.Show

随着 .NET Framework 1.0/1.1 的出现,您不能轻松地实现该功能。当您试图在 .NET Framework 1.0/1.1 中显示窗体时,需要以下代码:

Dim frm1 As New Form1()
frm1.Show()

使用 My.Forms,可以将项目中的所有窗体作为该集合的一部分轻松地进行访问。另外,还可以访问项目中所有窗体的默认实例,甚至不必使用 My.Forms。因此,下面的代码行与其是等效的:

My.Forms.Form2.Show()
Form2.Show()

在整本书中,您将看到许多使用这种显示窗体的方法。本书后面的部分将更加详细地介绍 My.WebServices和 My.Forms。

小结

对于作为开发人员,My 对象主要有四个用途。首先,它包括以前版本的 .NET Framework 中所没有的其他功能。其次,它提供一种有价值的快速拨号功能,使您可以在应用程序中更快地找到并使用 .NET Framework 基类库功能。第三,它填补了一些缺口,使您可以将事物(例如计算机)看作是逻辑实体。第四,它带回一些熟悉的编码语法,例如 Form1.Show

返回页首返回页首

应用程序:运算符

该应用程序引入新的运算符重载功能和 Visual Basic 语言新增的 IsNot运算符。

新概念

运算符重载允许您在使用内部语言运算符(例如 +、-、<、>、=、和 <>)时定义类的行为。在 Visual Basic 中,利用运算符(如 +)来操作对象不是一个新概念。例如,下面一行代码利用 + 运算符对两个字符串对象进行运算:str = “Hello, “+ “world”

应用于字符串时,+ 运算符连接两个字符串产生一个新的字符串。连接是对字符串进行加法运算后的适当结果。应用于自己类的实例时,Visual Basic 中的 Operator 重载允许您定义运算(例如加)的适当结果。

考虑一种需要确定一名员工是否比另一名员工工龄长的情况:

Dim emp1, emp2 As Employee
emp1 = Employee.GetFromID(123)
emp2 = Employee.GetFromID(155)

在该应用程序中,工龄是两个 Employee 对象之间的自然比较,因此下一行代码的语义应表示“如果 emp1 的工龄比 emp2 的长”。当用于两个 Employee 对象时,可以利用运算符重载来实际定义 >(大于)运算符的工作方式。一旦定义了 > 运算符,以下代码就是有效的:

If emp1 > emp2 Then
‘Add code here
End If

演练

该应用程序用Employee 对象填充两个列表框控件。然后,用户可以选择两个员工,看看哪个员工的工龄更长。对于该应用程序,工龄根据雇用的日期唯一确定。

运算符重载

重载运算符与创建新方法一样简单。事实上,运算符重载实际上就是用 Operator 关键字创建的方法。Employee 类的 > 运算符定义如下:

Public Shared Operator >(ByVal lhs As Employee, _
ByVal rhs As Employee) As Boolean
‘Add code here
End Operator

该运算符必须标记为 Shared,而且它至少有一个参数是封闭类型 (Employee) 的。> 运算符是二元运算符,这意味着它需要两个操作数:左侧操作数和右侧操作数。这两个操作数中只有一个必须是封闭类型的。如果想定义如何将 Employee 对象和其他内容(例如,一个整数)进行比较,那么另一个操作数可以是另一种类型。图 2-5 显示此代码的一种典型结果。


2-5 根据员工的雇用日期可以比较 Employee 对象。

传递给运算符方法的参数可以是Nothing(空引用),所以在试图访问属性或方法之前需要检查它们。Employee 类的 > 运算符的完整实现包含了处理空引用的逻辑:

Public Shared Operator >(ByVal lhs As Employee, _
ByVal rhs As Employee) As Boolean
If rhs Is Nothing Then
If lhs IsNot Nothing Then
Return True
Else
Return False
End If
End If
If lhs Is Nothing Then
Return False
End If
If rhs.HireDate > lhs.HireDate Then
Return True
Else
Return False
End If
End Operator

当定义某些特定的运算符时,Visual Basic 还要求定义该运算符的逆运算。如果定义 =(等于)运算符,则必须定义 <>(不等于)运算符。如果定义 >=(大于或等于)运算符,则必须定义 <=(小于或等于)运算符。如果定义 >(大于)运算符,则必须定义 <(小于)运算符。幸运的是,通常可以利用第一个运算符来定义逆运算符。因此,只需将操作数颠倒并使用 > 运算符,而不是编写 < 运算符的反逻辑:

Public Shared Operator <(ByVal lhs As Employee, _
ByVal rhs As Employee) As Boolean
Return rhs > lhs
End Operator

同样,<> 运算符返回 = 运算符的逻辑反:

Public Shared Operator <>(ByVal lhs As Employee, _
ByVal rhs As Employee) As Boolean
Return Not lhs = rhs
End Operator

IsNot 运算符

该应用程序中的 > 运算符重载方法利用了新的 IsNot运算符。在 Visual Basic 的以前版本中,要确定某个对象引用不是Nothing,需要将 Not 关键字应用于 Is 关键字:

If Not obj Is Nothing Then

虽然这种语法非常符合逻辑,但却难以使用,特别是试图读取和描述这种逻辑时。利用新的 IsNot运算符,下面的 If 语句是有效的:

If obj IsNot Nothing Then

这条语句与前面的If 语句在逻辑上完全相同,但读起来更自然:如果对象不是Nothing,那么……

IsTrueIsFalse和Not 运算符

Visual Basic 中的运算符重载包含重载名为 IsTrue和 IsFalse 的两个特殊运算符。这些运算符不能从代码中显式调用,但是编译器可将其用于计算表达式中某个对象的 true 或 false 值,该表达式预期返回一个 Boolean 值(例如 if 语句)。考虑下面的 Employee 对象的使用:

Dim emp As New Employee()
If emp Then
‘Add code here
End If

只有 Employee 的类定义中包含一种计算 Boolean 值的方法时,使用 emp 才有效。这可以通过定义一个 IsTrue运算符或 CType运算符来实现(将在下一节中描述)。Employee 的 IsTrue运算符返回 Employee.IsActive 字段的值或 false(对于空引用):

Public Shared Operator IsTrue(ByVal emp As Employee) As Boolean
If emp Is Nothing Then
Return False
Else
Return emp.IsActive
End If
End Operator

当定义 IsTrue 时,Visual Basic 还要求定义它的逆运算符 IsFalse。在像 Employee 类这样的简单情况下,定义这个逆运算符非常简单:

Public Shared Operator IsFalse(ByVal emp As Employee) As Boolean
Return Not emp
End Operator

但是,并不能随意地在 Employee 类中大量地使用 Not运算符。还必须定义该运算符:

Public Shared Operator Not(ByVal emp As Employee) As Boolean
If emp Then Return False Else Return True
End Operator

CType 运算符

在 Visual Basic 中可以重载的另一个特殊运算符是 CType运算符。CType 用于将表达式从其原来的数据类型转换为一种新的数据类型。例如,CType("100", Integer)将字符串转换为整数。通过为类重载 CType运算符,可以确切地定义类的实例如何转换为另一种数据类型。该应用程序中的 Employee 类有三种可能的转换:Employee stringEmployeedate,以及 Employeeboolean。对于每种转换,必须提供单独的 CType运算符重载:

Public Shared Widening Operator CType(ByVal emp As Employee) As String
Return emp.FirstName + ““+ emp.LastName
End Operator
Public Shared Widening Operator CType(ByVal emp As Employee) As Date
If emp Is Nothing Then
Return Nothing
Else
Return emp.HireDate
End If
End Operator
Public Shared Widening Operator CType(ByVal emp As Employee) As Boolean
If emp Is Nothing Then Return False Else Return emp.IsActive
End Operator

CType运算符重载可以标记为 Widening Narrowing。如果目标数据类型不能表示原始数据类型的所有可能值,转换就是收缩的。为 Employee 定义的转换都不是这种情况,因此它们都标记为 Widening

在使用 Option Strict On 时,Visual Basic 编译器不允许隐式收缩转换。

Employee 类中,重写 ToString方法,以便返回员工的姓和名,而不是类名。Employee 类有一个 CType运算符,它定义了从 Employee string 的扩大转换,因此 ToString 方法所需的代码量减到最少。从 Employee string 的转换是隐式的,因为该函数有一个显式返回类型 (string),并且定义从 Employee 对象到这种返回类型的扩大转换:

Public Overrides Function ToString() As String
Return Me
End Function

除非重新定义(重写) ToString,否则对象使用从 System.Object 继承的 ToString方法返回该对象的完全限定类名。

可以利用运算符重载创建一个有效、自然的接口来使用您的类。但是,参数和返回类型选择不当会导致应用程序编程接口 (API) 混乱。例如,大于 (>)、小于 (<)、等于 (=) 和不等于 (<>) 运算符一般应当返回 Boolean 值。返回 DateTime的 > 运算符在大多数情况下会引起混乱和不自然。二元 + 运算符通常应返回封闭类型,如一元 + 和 . 运算符一样:

Dim var1 As New MyCustomClass
Dim var2 As New MyCustomClass
Dim var3 As MyCustomClass = var1 + var2
var3 = -var3

对于运算符(例如二元 . 运算符),您可能希望返回不同的数据类型。例如,该应用程序中两个 Employee 对象的差可以是一个整数,它表示两个员工雇用日期的月份差值。

小结

运算符重载使您可以确定运算符(例如 = 和 >)在用于您定义的某个对象的两个实例时如何工作。要重载一个运算符,只需在类中创建一个包含 Operator 关键字的方法即可。重载运算符时,一定要定义它们的逆运算符。例如,重载 =(等于)运算符时,要定义对应的 <>(不等于)运算符重载。

规划良好、具有适当参数和返回类型的一组运算符重载可以使您的类更易于使用,并使源代码更简洁。

返回页首返回页首

应用程序:关键字

该应用程序引入新的 Visual Basic 关键字 Using、Global 和 Continue。

新概念

语言设计人员一般对向编程语言中添加新关键字比较保守。语言必须有足够的表达力才有用,但添加太多的关键字会使该语言太过复杂而难以掌握。随着 Visual Studio 2005 的发布而添加到 Visual Basic 中的新关键字,目的是在一些常见的编程方案中使代码更加清楚或简单。

Global 关键字

Visual Basic 允许创建您自己的命名空间以组织应用程序和库中的类。有意义的命名空间可以大大增加类层次结构的直观性和源代码的可读性。但是,如果命名的一个命名空间与 .NET Framework 基类库中的一个顶级命名空间重名,那么将不能访问这个顶级命名空间。例如,不能在自定义命名空间 Util.System 中将一个变量声明为 System.String,因为在 Util.System 中没有定义 String。Util.System命名空间隐蔽了顶级 System 命名空间。

当命名空间层次结构隐蔽了命名空间层次结构根的一部分时,Visual Basic 中新的 Global关键字允许您访问它。因此,Util.System 命名空间中的字符串变量可以声明为 Global.System.String。请参见“演练”一节中的示例。

Continue 关键字

大多数 Visual Basic 编程人员都遇到过这样的情形:某个循环的一些迭代被跳过。Visual Basic 的以前版本没有一种简单的方法可跳到循环的下一次迭代中。这意味着必须将整个循环体包装在条件块内。Visual Basic 中的 Continue关键字现在允许跳到循环的下一次迭代中,而无需处理循环体的其余部分。如果当前记录(在 SqlDataReader 中)的第一列为 Nothing,则下面的 While 循环将利用 Continue关键字跳到下一次迭代:

While dr.Read()
If dr(0) Is Nothing Then Continue While
‘Process current record in SqlDataReader
End While

可以在嵌套循环中使用 Continue关键字以跳到外层循环的下一次迭代:

While I < 1000
For J As Integer = 1 To 5
If I Mod J = 0 Then
Continue While
End If
Next
I += 1
End While

当内层循环和外层循环类型相同时,例如 For 循环嵌套在 For 循环中,Continue 关键字将应用于最内层的循环。

演练

Keywords应用程序基于 Util.System.IO命名空间中定义的 FileWriter类而构建:

Namespace Util.System.IO
Public Class FileWriter
Implements IDisposable
Private _outWriter As Global.System.IO.StreamWriter
Public Sub New(ByVal filename As String)
_outWriter = Global.System.IO.File.CreateText(filename)
_outWriter.WriteLine(“Output started at “+ Now.ToString())
End Sub
Public Sub WriteLine(ByVal message As String)
_outWriter.WriteLine(Now.ToString().PadRight(30) + message)
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
_outWriter.Close()
MsgBox(“StreamWriter closed by Dispose()”)
GC.SuppressFinalize(Me)
End Sub
Protected Overrides Sub Finalize()
_outWriter.Close()
MsgBox(“StreamWriter closed by Finalize()”)
End Sub
End Class
End Namespace

FileWriter类只是 StreamWriter 类的包装类。FileWriter对象为其写入文件的每一行添加当前的日期和时间。当使用者调用 FileWriter.Dispose 方法或者 .NET Framework 垃圾回收器调用 Finalize 方法时,将关闭 StreamWriter。

Dispose Finalize 方法包含 MsgBox调用,通知您何时调用每个方法。在该应用程序中,这些调用只是为了进行说明。通常情况下,不应当从非 UI 类(例如 FileWriter)显示消息框。

在图 2-6 中可以看到,Keywords 应用程序的用户界面是一个带有两个按钮的 Windows 窗体。一个按钮的单击事件处理程序使用一条包含 FileWriter对象的 Using语句。第二个按钮的单击事件处理程序没有使用 Using 也没有调用 Dispose — 它依靠垃圾回收器来调用 Finalize

Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles Button1.Click
Dim fw As New _
Util.System.IO.FileWriter( _
System.Environment.CurrentDirectory + “\button1.txt”)
Using (fw)
For I As Integer = 1 To 100
If I Mod 3 = 0 Then
Continue For
End If
fw.WriteLine(CStr(I))
Next
End Using
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles Button2.Click
Dim fw As New _
Util.System.IO.FileWriter( _
System.Environment.CurrentDirectory + “\button2.txt”)
For I As Integer = 1 To 100
If I Mod 3 = 0 Then
Continue For
End If
fw.WriteLine(CStr(I))
Next
End Sub

2-6 Keywords 应用程序用户界面。

在这两种情况中,单击事件处理程序利用 FileWriter 类将 1 到 100 中的每个整数(除了能被 3 整除的值之外)都写入文件。

使用 Global 关键字

FileWriter 类使用来自基类库 System.IO命名空间的 StreamWriter。但不能从类定义中直接访问 System.IO,因为 FileWriter在 System.IO命名空间 (Util.System.IO) 中定义,它在命名空间层次结构的根部隐蔽了 System.IO命名空间。要解决这种命名冲突,可以使用 Global关键字来访问基类库中的 System.IO:

Private _outWriter As Global.System.IO.StreamWriter

Util.System.IO命名空间不包含 StreamWriter类,因此将变量声明为 System.IO.StreamWriter或 IO.StreamWriter将不能编译。但是,使用 Global关键字允许编译器将该类解析为 .NET Framework 类库的正确类型。

使用 Using 关键字

当创建 FileWriter对象(即在它的构造函数中)时,该对象打开其内部的 StreamWriter并保持 StreamWriter处于打开状态,直到调用 Dispose Finalize 为止。在 Button1_Click事件处理程序中,Using关键字应用于 FileWriter对象 fw。在 Using 块的末尾,自动调用 fw.Dispose,fw.Dispose 将关闭基础 Stream-Writer 对象。结果如图 2-7 所示。


2-7 自动调用 Dispose 后显示的消息框。

在 Using块之后立即调用 Dispose,因此在本例中立即显示 Dispose 的消息框。相反,第二个按钮创建一个 FileWriter,但是未调用 Dispose。当垃圾回收器清理对象时,Finalize 方法的消息框仿佛会随时出现。对于这个简单的应用程序,消息框最有可能在关闭程序时出现。

使用 Continue 关键字

该应用程序中按钮的单击事件处理程序都是将 1 到 100 中不能被 3 整除的整数写入文件:

For I As Integer = 1 To 100
If I Mod 3 = 0 Then
Continue For
End If
fw.WriteLine(CStr(I))
Next

生成数字的 For 循环简单说明了新的 Continue关键字的使用情况。如果 I Mod 3 为 0,则当前的 I 值被 3 整除。当该条件为真时,Continue For 将执行转移到循环的下一次迭代,而不处理 For 循环的剩余部分。例如,如果 I 是 6,则执行 Continue For,将迭代转移回循环的顶部,并且使 I 等于 7。输出结果的部分清单如下所示(注意,没有 3、6、9 和 12):

Output started at 12/12/2003 8:06:42 AM
/12/2003 8:06:42 AM 1
/12/2003 8:06:42 AM 2
/12/2003 8:06:42 AM 4
/12/2003 8:06:42 AM 5
/12/2003 8:06:42 AM 7
/12/2003 8:06:42 AM 8
/12/2003 8:06:42 AM 10
/12/2003 8:06:42 AM 11
/12/2003 8:06:42 AM 13

小结

该应用程序中演示的新关键字 — Continue、Global 和 Using — 为 Visual Basic 语言增添了更多的方便性和灵活性。Continue关键字有助于减少循环中复杂的逻辑,以处理您不想处理的特殊情况。Global关键字使您能够灵活地使用任何类层次结构的命名空间名称,而不会使得来自 .NET Framework 类库的命名空间不可用。而 Using关键字使您不必在程序中每个执行路径的末尾显式调用 Dispose。这些新关键字都是 Visual Basic 语言增加的变化。它们不会显著改变编写代码的方式,但在某些情况下,它们有助于使源代码更加简单或清晰。

posted on 2006-10-07 13:54  天使爱比目鱼  阅读(1292)  评论(0编辑  收藏  举报