如果你以前经常使用Visual Basic的话,你会发现VisualBasic .Net有点熟悉,又有一点陌生。在可以利用许多以前就具备的技巧和知识的同时,仍然有很多东西要学。
许多公司花费了大笔的资金投资于Visual Basic6.0编写的软件,这些公司将面临如何处理这些已存在的软件的问题。我在介绍Visual Basic .NET新特点的同时,将会介绍影响把代码移植到Visual Basic .NET的关键所在。正如你将看到的,会有很多问题要考虑。随着编程语言变得更加一致、强大和友好,你会发现把Visual Basic 6.0项目改写成Visual Basic .NET项目需要很大的努力和技术。如果顺利的话,你将有幸在不远的将来从零开始编写一个VisualBasic .NET的项目。
新的便利:
我首先将介绍用一行代码实现声明并初始化变量的新语法。尽管许多初学者会认为这种语法是理所当然的,但实际上那些已习惯于在Visual Basic用两行代码来声明和初始化变量的程序员会对此感到很欣慰。
这儿是利用了Visual Basic .NET语法优势的三个例子:
Dim x As Integer = 10
Dim obj1 As Class1 = New Class1
Dim obj2 As New Class2
请注意,与前两行不一样,最后一行用的是在Visual Basic 6.0中合法的语法,它在Visual Basic .NET中同样是合法的。然而,值得注意的是,这儿的As New语句在Visual Basic .NET and Visual Basic 6.0中的处理是不同的,许多有经验的程序员反对使用As New语句,因为它将导致初始化的延迟,进而导致较低的执行效率,并给调试程序带来了许多困难。
好消息是As New语法在Visual Basic .NET中并不会造成初始化的延迟,因此不会导致相同的问题。在看前面的例子时,你应该注意第三行,它用了As New语句,但与前面几行具有一样的执行效率。当你在Visual Basic .NET中使用As New语句时,这个对象将在执行下一行之前被创建、初始化,并指派给你的变量。
这种方便的初始化语法也可以被用在类或结构的定义中。正如你想得那样,语法应该是这样的:
Class Class1
Private Field1 As Integer = 10
Public Field2 As Class1 = New Class1
End Class
另一点值得注意的是,当你在同一行声明几个变量时,他们应该是相同类型的变量,请看这个例子:
Dim x, y, z As Integer
三个变量都被声明成integer类型,你不再需要担心前两个变量偶然会被当作variant类型。实际上,你不用担心有什么变量会被当作variant类型处理,因为variant类型不再被CLR编程模型所支持。现在的通用类型是System.Object.
在Visual Basic .NET新增加的语法中,我最喜欢的一点是现在函数可以用Return来向它的调用者返回一个值。请看下面的例子:
Function MyFunction() As String
Return "This is my return value"
End Function
这个例子和用其他语言(比如C语言)一样,Return语句将结束函数的执行,并把控制权返回给调用者。我觉得使用Return语句与我们在VisualBasic的早期版本中的做法相比要方便得多。正如你记得的,在以前的版本中,要返回给调用者的值必须在函数的内部被赋给函数名。由于在VisualBasic .NET中使用了Return语句,你可以很容易地实现更改函数名或把代码从一个函数中复制、粘贴到另一个函数,而不需要搜索函数的内容并替换原来的函数名。
消除矛盾
正如我在二月份的文章中提到的那样,在把一个对象指派给一个变量时,Visual Basic .NET既不需要,也不允许你用Set这个关键字。VisualBasic 6.0 和Visual Basic .NET的编译器有许多方面不一样,这就是其中之一。请看下面的代码:
Dim obj1, obj2, obj3, obj4 As Class1
obj1 = New Class1
obj2 = obj1
Set obj3 = New Class1
Set obj4 = obj3
第二行和第三行代码在Visual Basic .NET中是合法的。但在VisualBasic 6.0中编译时,它们将导致运行期错误。第四行和第五行的代码在VisualBasic 6.0中是合法的,但在Visual Basic .NET中却不能通过编译。正如你想象的,在把代码从VisualBasic 6.0移植到Visual Basic.NET时,需要特别注意对Set语句的改写。
在用Visual Basic .NET编写了六个多月的程序后,我发现自己仍然会使用Set语句,老习惯是不容易被改掉的。而且由于工作的关系,有好几次我得在同一天即用VisualBasic.NET,又用Visual Basic 6.0编写程序。不停地来回往复真地很困难。
在参数传递中括号的使用也发生了变化,这也给代码移植增加了困难。你们中的许多人在经过多年的编程之后已经习惯了调用函数和子程序的规则和特点。VisualBasic.NET小组觉得应该实现调用方法的一致性。
在Visual Basic. NET中,参数传递的规则很简单。当你调用一个函数或子程序并需要传递一个或多个参数时,必须用小括号把参数括起来。当调用一个不需要参数的函数或子程序时,小括号可以被省略。你不得不承认,与早期版本的VisualBasic中的规则相比,这些规则更直截了当,也更容易去学。
值得注意另一点是参数传递的默认方式也改变了。比如,当一个方法被定义成下面这种样式时,参数是如何被传给调用者的。
Sub Method1(Param1 As Integer)
' implementation
End Sub
在Visual Basic以前的版本中,Param1是引用传递的。也就是说在执行Method1时,改变这个integer类型的参数将导致调用者中相应的实际参数的值的改变。而在VisualBasic.NET中,默认的参数传递方式变成了值传递。也就是说执行Method1时,只是值被传递给了Param1,并且在Method1中改变Parame1的值不会改变调用者中相应的实际参数的值。很明显,在把VisualBasic 6.0中代码移植到Visual Basic.NET,这种参数传递默认方式的改变将破坏一些代码的语义。
请注意,在上一个例子中,被传递到参数的是一个值而不是地址。基本类型比如Byte,Integer,Double, and Boolean都是值类型。用户定义的枚举和结构类型也是值类型。在以值传递方式传递时,变量总是被复制。
当你传递的参数是基于类时,情况就不同了。基于类的参数是引用类型而不是值类型。它们被用来传递对象的引用。如果一个调用者传递了一个对象的引用给一个用了值传递方式的方法,在这个方法的实现中,对象的状态可能被改变。这种状态的改变将反映到调用者中。因此,无论你把参数声明成值传递还是引用传递,注意值类型还是引用类型之间的区别是很重要的。
尽管在方法中仍然可以使用可选参数,这仍是值得注意的地方。与VisualBasic 6.0不同,使用可选参数需要一个默认值。在同一行中,Visual Basic.NET不支持IsMissing函数,也就是说你区分不出一个省略了参数的调用和一个给参数传了默认值的调用。
如果你考虑在方法中使用可选参数,你应该考虑重载方法。一旦你知道了重载方法的好处和语法,你也许就会停止在方法中使用可选参数。
更高级别的类型保护
许多使用Visual Basic的开发者希望在每一个VisualBasic的源文件的开头能有一个Option Explicit语句。Option Explicit的应用可以区别对待真正的开发者和临时用户。和以前的版本不同,Visual Basic.NET缺省就使用Option Explicit。
在编译时默认使用Option Strict是Visual Basic.NET的另一个特点。使用了Option Strict后,缩小转换就被禁止了,因为它可能会导致数据或精度的丢失。请看下面的例子:
Dim x As Double
x = 20.1
Dim y As Integer
' implicit narrowing conversion
y = x ' doesn't compile under Option Strict
这段代码在Visual Basic 6.0可以被编译通过,但在Visual Basic.NET中,如果Option Strict语句被使用,它就不能被编译通过。Option Strict的使用使得程序员必须对应该使用何种类型很清楚。
Dim x As Double
x = 20.1
Dim y As Integer
' explicit narrowing conversion
y = CInt(x) ' compiles under Option Strict
另一个很大的变化是现在If语句被优化了。这代表了一个很有价值的最优化,而且现在当把代码移植到Visual Basic.NET时,理解这将如何影响基于Visual Basic 6.0的代码变得很重要。请看下面的代码:
If Function1() And Function2() Then
' statement block
End If
如果Function1返回了False值,那需要执行Function2以决定是否要执行IfThen中的代码吗?很明显这是不需要的。然而早期版本的Visual Basic并没有像这样优化If语句,在任何情况下Function2都将被执行。VisualBasic.NET实现了优化,因此当Function1返回值为False时,Function2不会被执行。这是另一个使得基于VisualBasic 6.0的代码不能在Visual Basic.NET中正确执行的因素。
现在我想谈谈这个语言中在类型安全方面的另一个改进。你应该注意到,如果Function1或Function2返回的值不是Boolean,那么前面的代码不能通过编译。由于类型安全的级别更高了,你不可以用integer类型的值进行条件测试了。比如下面的这段代码,在Visual Basic 6.0中是被经常使用的,但在Visual Basic.NET中却不能通过编译。
Dim x As Integer
' set x to 0 for false or anything else for true
If x Then
' execute statement block
End If
这种由Option Strict带来的新的类型安全级别使得你在使用If和Doloops语句时,必须使用Boolean类型的值,而不能用其它类型的值。
在使用诸如Not、 And、 Or、 Xor等的逻辑比较符时,你也被限制必须使用Boolean类型的值来输入输出。这与在以前版本的VisualBasic中这些比较符既可以用于逻辑比较又可以用于按位比较不同。下面这段代码在VisualBasic 6.0中能正常运行,但在Visual Basic .NET中却不能通过编译。
Dim x As Integer
Dim y As Integer
Dim z As Integer
x = 3
Y = 6
z = x And y ' doesn't compile
Visual Basic.NET提供了四种新的按位比较运算符:BitNot,BitAnd, BitOr, 和BitXor。这些运算符允许你在类型安全的方式下实行按位比较运算。比如,你可以用这些运算符中的一个对两个integer值的每一位来实现OR运算,还可以实现位掩码。下面是使用BitOr和BitAnd的一个例子:
Dim x As Integer = 3
Dim y As Integer = 6
Dim z As Integer
' OR bits together
z = x BitOr y ' z now equals 7
' AND to find intersection of bits
z = x BitAnd y ' z now equal 2
设计数组
声明和使用数组的基本语法发生了很大的变化。首先,数组的下界别成了零。你不可以声明一个下界为1的数组。因此,OptionBase语句不再被VisualBasic .NET支持。
另外,你在声明一个数组时必须用它的元素个数,而不是它的上界来初始化。这儿是一个例子:
' declare an array with 3 elements from 0 to 2
Dim array1(3) As Integer
array1(0) = 2
array1(1) = 4
array1(2) = 8
上一段声明如果在Visual Basic 6.0中被使用,这个数组将有四个元素,下标从0到3。而在VisualBasic.NET中,这个数组有三个元素,下标从0到2。在VisualBasic .NET中,如果你的代码企图访问下标为3的数组元素,将引起运行期例外。比如:
' index out of range exception
array1(3) = 16
Visual Basic.NET为初始化数组提供了一种新的语法。你可以只用一行代码完成数组的声明和初始化。就像这样:
new array initialization syntax
Dim array1 As Integer() = {2, 4, 8}
'
在Visual Basic的早期版本中,你可以用For Each循环遍历一个数组。
Dim x As Integer
For Each x In array1
Console.WriteLine(x)
Next
同时,你也可以用For循环和数组长度来遍历一个数组。比如:
Dim i As Integer
For i = 0 To (array1.Length - 1)
Console.WriteLine(array1(i))
Next i
CLR 和Visual Basic.NET还支持多维数组。比如,当你想要一个二维数组时,你可以用下面三个技术中的一个来定义它:
Dim array1(2, 2) As Integer
Dim array2 As Integer(,)
Redim array2(2, 2)
Dim array3 As Integer(,) = { {12, 24}, {10, 20} }
使用数组时,你应该知道,不仅仅是语法变了,在运行时被处理的方式也发生了变化。在CLR中,数组被分配的地址空间总是在堆中。当你向一个方法传递数组类型的参数时,你使用的引用传递而不是值传递。
这儿是互相传递数组引用的三个方法:
Dim array1(2, 2) As Integer
Dim array2 As Integer(,)
Redim array2(2, 2)
Dim array3 As Integer(,) = { {12, 24}, {10, 20} }
Method1在两个方向同时传递了数组引用,这一般用来向调用者返回数组引用。
Method2 和 Method3 从调用者向方法的实现中传递了数组引用。请注意Method2的参数被声明一维数组,而在Method3中参数被声明成了二维数组。
每一个数组对象继承了系统支持的类System.Array的核心细节实现和公共成员。你可以参考在.NET platform SDK中有关System.Array的文档。这个类提供了面向一般数组的任务的函数,比如清空、复制、克隆、搜索和排序。这儿是使用System.Array类提供的一些方法的例子:
Dim array1 As Integer() = {4, 2, 8, 1}
Dim array2 As Integer()
' cloning an array
array2 = CType(array1.Clone(), Integer())
' clearing an array
System.Array.Clear(array1, 0, array1.Length)
' sorting an array
System.Array.Sort(array2)
' sorting an array in reverse order
System.Array.Reverse(array2)
请注意,你可以对任何基于IComparable界面实现的数组使用排序操作。CLR的核心类型,比如Integer,Double, String, and Date,都被设计成实现IComparable的了。如果你想把数组按你想要的方式进行排序,你只要在类和结构中实现IComparable接口就可以了。
属性的新语法:
我相信你对在早期版本的Visual Basic如何定义和使用属性很熟悉。尽管使用属性的原因是相同的,但定义属性的语法已经变了。属性现在必须用包括Set块或Get块的属性构造器来定义。
下面是一个有private字段和public属性的类的例子:
Class Class1
' private field
Private m_Name As String
' public property
Public Property Name As String
Get
Return m_Name
End Get
Set
If Value <> "" Then ' Value is intrinsic variable
m_Name = Value
End If
End Set
End Property
End Class
请注意Set块中包含了内部变量Value。VisualBasic.NET用这个内部变量向你在Set块中的属性实现传递了由客户制定的属性值。另外,在Visual Basic.NET中,程序员不再会有何时应使用Set属性,而何时应使用Let属性的困惑了。
一个属性一定是ReadWrite, ReadOnly, 或 WriteOnly的。上面的例子演示了ReadWrite的属性。下面的例子将演示如何创建ReadOnly和 WriteOnly的属性。
' ReadOnly property has Get but no Set
Public ReadOnly Property FullName as String
Get
Return m_FirstName & " " &m_LastName
End Get
End Property
' WriteOnly property has Set but no Get
Public WriteOnly Property Password as String
Set
m_Password = Value
End Set
End Property
请注意,当你省略了Get块或Set块时,你一定要使用ReadOnly或WriteOnly关键字
索引属性和默认属性
一个属性可以被指定一个或多个索引。这可以使属性具有数组的特点。请看下面的类:
Class Class1
Private m_Names As String() = {"Ted", "Fred", "Jed"}
' an indexed property
Readonly Property Item(Index As Integer) As String
Get
Return m_Names(Index)
End Get
End Property
End Class
在客户端,你可以用下面的例子访问Item属性。
Dim obj As New Class1
Dim s1 String
s1 = obj.Item(0)
如果把这个例子作进一步的改进,你可以把一个索引属性标记为类的默认属性。为了实现这一点,只要把Default关键字加到上一个例子中就可以了,就像这样:
Default Readonly Property Item(Index As Integer)…
一旦你为一个索引属性标记了Default关键字,客户端的代码就可以省略属性的名字而使用对象引用,就像使用一个数组一样。
Dim obj As New Class1
Dim s1 String
s1 = obj(0)
请注意,对于默认属性有一个重要的限制,即非索引属性不可以被标记为默认属性。这个限制在Visual Basic.NET中体现了出来,因为你不可以用Set语句来指定一个对象引用。既然Set语句不再被Visual Basic.NET所支持,那么非索引的默认属性将导致无法解决的多义性问题。
小结
正如你所见到的那样,在许多方面Visual Basic.NET都与它以前的版本不同。它具有更高的一致性和类型安全级别。另外,用Visual Basic.NET我们更容易写出控制性、可读性很高的代码。虽然它有时要求你习惯于那些编译时额外的检查,但它将在你测试和调试时,为你节约宝贵的时间。
好消息是Visual Basic已经被改成了一种更好、更强有力的语言。而坏消息是在Visual Basic 6.0下编写的软件需要做很多工作才可以被移植到Visual Basic.
……