【IT168 技术文档】
空类型
关系数据库引入了空值的语义,空值和当前的普通程序设计语言经常不一致,程序员也不熟悉它。在以数据为中心的程序中,正确清晰地理解这些语义是很重要的。考虑到这一点,在"Whidbey"里CLR使用泛型Nullable(Of T As Struct)加入了对空特性的运行时支持。使用这个类型我们可以声明各种值类型的空版本,如Integer,Boolean和Data等等。为了明显起见,VB中空类型的语法为T?。
举例来说,并不是所有国家都是独立的,我们可以给类Country加上一个新的成员代表他们的独立时间:
Partial Class
Country Public Property Independence As Date?
End Class
和数组类型一样,我们也可以给属性名加上可为空的修饰语,如下声明:
Partial Class
Country Public Property Independence? As Date
End Class
帕劳群岛的独立时间是#10/1/1994#,不过Virgin岛是英联邦的属地,因而它的独立日是Nothing:
Dim Palau = _
New Country { _
.Name = "Palau", _
.Area = 458, _
.Population = 16952, _
.Independence = #10/1/1994# }
Dim VirginIslands = _
New Country { _
.Name = "Virgin Islands", _
.Area = 150, _
.Population= 13195, _
.Independence = Nothing }
Visual Basic 9.0支持在空值上的三值逻辑和空传播算术,这意味着如果其中一个算术,比较,逻辑或者位,转换,或者类型操作的操作数是Nothing,结果就是Nothing。如果两个操作数都是适当的值,操作将根据操作数的值来决定,结果被转化为空。
因为Palau.Independence和VirginIslands.Independence都是Date?类型,编译器将使用空传播算术来进行下面的提取,因此对于本地声明PLength和VILength的推断类型就都是TimeSpan?。
Dim PLength = #8/24/2005# - Palau.Independence REM 3980.00:00:00
PLength的值是3980.00:00:00因为没有操作数是Nothing。另外,由于VirginIslands.Independence的值是Nothing,结果类型也是TimeSpan?,但是因为空传播的原因VILength的值是Nothing。
Dim VILength = #8/24/2005# - VirginIslands.Independence REM Nothing
在SQL里,比较操作符将会做空传播,逻辑员算府将会使用三值逻辑。在If和While语句中,Nothing将会理解为False;因此在如下代码里,使用了Else分支:
If VILength < TimeSpan.FromDays(10000)
...
Else
...
End If
注意在三值逻辑里,等式检查X = Nothing,但是Nothing = X总是等同于Nothing;为了检查X是否是Nothing,我们应该用双值逻辑比较X Is Nothing或者Nothing Is X。
在打包和解包时,运行时将会把控制看作是Object来处理。当打包一个代表Nothing的空值时(HasValue属性为False),值将会被打包进一个空引用。当打包一个合适的值时(HasValue属性为True),当前值将会首先被解开,然后再打包。因为这样,没有堆里的对象是动态类型Nullable(Of T);所有这样明显的类型都是T。同时,我们也可以从Object中解包值然后放入到T或者Nullable(Of T)。然而,这样做的结果是后期绑定不能动态的决定是否使用三值逻辑或者双值逻辑。举例来说,当我们做一个两个数的早期绑定的相加时,一个数是Nothing,将使用空操作,结果就是Nothing:
Dim A As Integer? = Nothing
Dim B As Integer? = 4711
Dim C As Integer? = A+B
REM C = Nothing
然而,当使用两个值的后期绑定相加时,结果是4711,因为A和B的动态类型都是Integer而不是Integer?,后期绑定将会使用双值逻辑。这里Nothing被解释为0:
Dim X As Object = A
Dim Y As Object = B
Dim Z As Object = X+Y
REM Z = 4711
为了保证正确地语义,我们需要引导编译器使用空传播重载:
Operator +(x As Object?, y As Object?) As Object?
通过使用?操作符转化任意一个操作数为空类型:
Dim X As Object = A
Dim Y As Object = B
Dim Z As Object? = X?+Y
REM Z = Nothing
注意这里我们必须能够从任何类型T来创建T?。当前的CLR Nullable(Of T As Struct)类型限制只能使用非空结构的参数类型。T如果不是一个非空值的类型,Visual Basic编译器将会把T?转化为T,否则转化为Nullable(Of T)。编译器将会在VB程序里保留足够的内部元数据以记住载此两种情况下静态类型都是T?。
非严格委托
当使用AddressOf或者Handles在Visual Basic 8.0创建一个委托时,要和委托标识符绑定的方法之一必须要和委托类型的署名相一致。在如下例子中,OnClick子程序的署名必须和事件句柄委托Delegate Sub EventHandler(sender As Object, e As EventArgs)一致,该委托是在Button类型后声明的:
Dim WithEvents B As New Button()
Sub OnClick(sender As Object, e As EventArgs) Handles B.Click
MessageBox.Show("Hello World from" + B.Text)
End Sub
然而,当调用非委托函数和子程序时,Visual Basic并不需要实际的参数严格符合我们想要调用的某个方法。就和下面的程序段显示的一样,我们实际上使用了一个Button类型和MouseEventArgs类型作为参数来调用OnClick子函数,它们分别是前面参数Object和EventArgs的子类型:
Dim M As New MouseEventArgs(MouseButtons.Left, 2, 47, 11,0) OnClick(B, M)
相反的,假设我们定义了一个子程序RelaxedOnClick,它接受两个Object参数,那么我们允许使用Object和EventArgs类型的参数来调用它:
Sub RelaxedOnClick(sender As Object, e As Object) Handles B.Click
MessageBox.Show("Hello World from" + B.Text))
End Sub
Dim E As EventArgs = M
Dim S As Object = BRelaxedOnClick(B,E)
在Visual Basic9.0里,和委托绑定和方法调用是非严格一致的。也就是说,如果有可能去使用严格符合一个委托的原定参数和返回类型的参数来调用一个函数或者子程序,我们就可以将函数和子程序同委托绑定。委托绑定和定义将会参照之后的方法调用采用相同的重载解析逻辑。
这暗示了在Visual Basic 9.0中我们可以将一个接受两个Object参数的子程序RelaxedOnClick和一个Button的Click事件绑定起来:
Sub RelaxedOnClick(sender As Object, e As Object) Handles B.Click
MessageBox.Show(("Hello World from" + B.Text)
End Sub
事件句柄的两个参数,sender和EventArgs,没有太大的关系。句柄将会通过事件直接注册的control来访问control的状态,忽略掉它的两个参数。为了支持一般的情况,委托可以非严格的不接受参数,如果没有混淆结果的话。因此,我们可以这样简写如下:
Sub RelaxedOnClick Handles B.Click
MessageBox.Show("Hello World from" + B.Text)
End Sub
现在比较清楚的是,非严格委托也应用在使用AddressOf创建委托或者委托创建表达式里,特别是方法组是一个后期绑定的调用时:
Dim F As EventHandler = AddressOf RelaxedOnClick
Dim G As New EventHandler(AddressOf B.Click)
动态接口
在纯粹的静态类型语言如C#或者Java或者打开了选项Strict的Visual Basic中,我们只能在一个存在于编译时的目标表达式上调用成员。举例来说,下面的第二个赋值将会导致一个编译时错误,因为类Country没有一个Inflation成员:
Dim Palau As Country = Countries(0)
Dim Inflation = Country.Inflation
然而,在很多情况下,我们需要在并不知道目标类型是静态的情况访问一个成员。如果把选项Strict关闭,Visual Basic将会允许后期绑定成员访问Object类型的目标。尽管功能强大并且也非常灵活,后期绑定还是会带来一些消耗。特别的,我们失去了智能感知和类型推断,只能通过cast或者显式类型回到前期绑定的世界。
经常在进行后期绑定调用的时候,我们假设值是和某个"接口"相依附的。只要对象满足这个接口,我们就高兴了。动态语言社区称此为"Duck Typing"。如果像鸭子一样走路并且像鸭子一样讲话,那么它就是鸭子了。为了解释这一点,下面的例子将会随机的返回Country或者一个新的代表人的匿名类型,它们都具有一个Name属性是String类型的:
Function NamedThing() As Object
Dim Static RandomNumbers = New Random()
Dim I = RandomNumbers.Next(9)
If I < 5
NamedThing = Countries(I)
Else
NamedThing = New{ .Name = "John Doe", .Age = 42-I }
End If
End Function
Dim Name = CStr(NamedThing().Name)
当我们调用CStr(NamedThing())进行后期绑定的时候,我们假设NamedThing()的返回值有一个Name成员类型是String。使用新的动态接口的特性,我们可以很明晰的做到这一点。访问其静态类型是一个动态接口的目标的最经常的方式就是使用后期绑定,但是成员访问是静态类型的。这意味着我们得到了全部的智能感知和类型推断,不需要做任何的cast操作或者显式类型:
<Dynamic> _
Interface IHasName
Property Name As String
End Interface
Dim Thing As IHasName = NamedThing()
Dim Name = Thing.Name
REM Inferred As String.
动态标识符
动态接口揭示了一个事实,程序员假设他们知道将要在后期绑定中调用的成员的名称和标记。然而在一些完全动态的场景下,我们可能不知道我们将要静态调用的成员的名称。当一个调用表达式的标识符是动态计算出时,动态标识符允许后期绑定调用。
下面的例子使用了三种不同的语言--英语,荷兰语,法语的三个类,每个类都有一个"名称"域:
Class English
Name As String
End Class
Class Nederlands
Naam As String
End Class
Class Francais
Nom As String
End Class
为了访问一个人的这个"Name"域,我们将获取这个类型的名字值,并且在一个表里查询这个成员的名字。我们可以使用动态标识符访问来调用正确的Person的成员:
Dim Dictionary = New Dictionary(Of String, String) { _
.Add("English", "Name"), _
.Add("Nederlands", "Naam"), _
.Add("Francais", "Nom") }
Dim Person As Object = New Francais { .Nom = "Eiffel" }
Dim Name As String = Person.(Dictionary(Person.GetType().Name))
结束语
Visual Basic9.0引进了很多新的特性。在这个文档里,我们列出了一些相关的例子来解释这些特性,不过下面的主题也值得一提:
·关系,对象和XML数据:Visual Basic 9.0统一的访问数据,无论数据源是在关系数据库里,XML文档里,还是任意的对象图里,是持续保存的还是只是在内存中暂存。这个统一包括样式,技术,工具和编程方式。这个Visual Basic的动态语法使得在语言里加入一些诸如XML literals和与SQL类似的查询综合变得很容易。这也减少了新的.net语言里集成的查询API的"表面区域",增加了诸如智能感知和聪明标记的一些数据访问特性的发现能力,通过提升字符串数据中的外国语法进入本地语言极大的提高了调试嗯农国里。在这个特性里,我们希望通过进一步提升XSD方案来增加XML数据的一致性。
·静态类型及其增加的动态特性:静态类型的好处是人所共知的:在编译时而非运行时标出bug,通过前期绑定访问达到高性能,程序代码清晰等等。然而,有些时候,动态类型使得代码更加短小,清楚和灵活。如果语言并不直接支持动态类型,当程序员需要的时候,他们就会通过反射,字典,分派表等等技术来一点点实现动态结构。这就会造成bug的出现和维护费用的增加。Visual Basic对动态和静态类型的支持,将这两个美好的世界传送给了程序员。
·减少程序员负担:一些诸如类型推断,对象初始化和非严格委托的特性在不影响性能的同时,极大的减少少了代码冗余和其他一些程序员需要学习,查找和记忆的规则。一些诸如甚至在后期绑定的情况下支持智能感知的动态接口的特性,增加了高级特性的发现能力。
尽管看起来Visual Basic9.0列出了一堆新特性,我们希望以上的主题将会使你更加方便的,舒服的使用它,并且使得VB成为世界上最好的编程系统。我们希望你的灵感也被激发,并且和我们一样认识到着确实是一个更加伟大的事情的开始。