![]() |
专题推荐:
Mysql初级教程 |
MySQL数据库技术强化学习 |
nginx负载平衡 |
搜索引擎优化(SEO) |
Linux命令详解 |
Linux网管 |
常用手册: MySQL4.1中文手册 | HTML4.0参考手册 | APACHE 2.0中文在线手册 | MySQL 5.1参考手册 | FreeBSD 7.0使用手册 |
前言
"Visual Basic code-named Orcas"(Visual Basic 9.0)对基于"Visual Basic code-named Whidbey"(Visual Basic 8.0)进行了一些语言方面的扩展,以统一的方式支持以数据为中心的编程--创建,更新和查询关系数据库,XML文档和对象图。与此同时,VB9.0也引进了一些新的语言特性,来加强VB对静态类型和动态类型的支持。这些新特性包括:
开始使用Visual Basic9.0
为了体验这些语言的强大特性,我们可以从一个真实世界的例子看起--CIA World Factbook database。这个数据库包含了世界上各个国家的各种地理的,经济的,社会的和政治信息。为了我们例子的方便,我们从定义一个国家和其首都,总面积和人口入手。我们在VB9.0中使用如下类进行编程:
Class Country
Public Property Name As String
Public Property Area As Float
Public Property Population As Integer
End Class
这是一个可以用来运行例子的国家数据库的子集:
Dim Countries =
_ { new Country{ _
.Name = "Palau", .Area = 458, .Population = 16952 },_
new Country{ _
.Name = "Monaco", .Area = 1.9, .Population = 31719 },_
new Country{ _
.Name = "Belize", .Area = 22960, .Population = 219296 },_
new Country{ _
.Name = "Madagascar", .Area = 587040, .Population = 13670507 }
_ }
从这个列表中,我们可以使用如下的查询综合查询那个国家的人口少于100万:
Dim SmallCountries = Select Country _
From Country In Countries _
Where Country.Population < 1000000
For Each Country As Country In SmallCountries
Console.WriteLine(Country.Name)
Next
因为只有马达加斯加拥有100万以上的人口,因此以上程序的结果为:
PalauMonacoBelize
让我们检查一下程序,理解一下VB9.0的特性,看看是什么使得编程如此的简单。首先,定义Conuntries变量:
Dim Countries = _
{ new County { .Name = "Palau", .Area = 458, .Population = 16952 }, _
... _
}
使用新的对象初始化语法new Country {..., .Area = 458, ...}来通过一个简洁的,基于表达式的语法创建一个复杂的对象实例,这和已有的With语句有些类似。
这个声明也使用了隐式类型的局部变量声明,编译器通过该声明中等号右边的初始化表达式来推断处本地变量Countries的类型。以上的声明和一个显式类型本地变量的类型Country()的声明的效果是一样的。
Dim Countries As Country() = {...}
重复一下,这里仍然是一个强类型声明;编译器自动推断等号右边的本地声明的类型,不需要程序员手动输入类型。
本地变量声明SmallCountries使用一个SQL风格的查询综合 来过滤掉人口小于100万的国家。它本身类似于SQL,使得熟悉SQL的程序员可以很快的熟悉和使用VB的查询语法。
Dim SmallCountries = Select Country _
From Country In Countries _
Where Country.Population < 1000000
注意我们还有一个隐式类型的应用:编译器推断SmallCountries的类型为IEnumberable(Of Country)。编译器把查询综合转变为标准的查询操作。这里,转换过程可能是如下这样简单:
Function F(Country As Country) As Boolean
Return Country.Population < 1000000
End FunctionDim
SmallCountries As IEnumerable(Of Country) = _ Countries.Where(AddressOf F)
这个扩展的表达式把编译器产生的本地函数作为一个委托Addressof F传递给表达式函数Where,Where在标准的查询操作库里定义为一个IEnumerable(of T)接口扩展。
下面我们对VB9的特性做一个深入的探究。
--------------------------------------------------------------------------------
隐式类型本地变量
在一个隐式类型本地变量的声明中,本地变量的类型是从等号右边的本地声明语句的初始化表达式中推断出来的。举例来说,编译器推断如下的变量声明:
Dim Population = 31719
Dim Name = "Belize"
Dim Area = 1.9
Dim Country = New Country{ .Name = "Palau", ...}
因此它们等同于如下的显式类型声明:
Dim Population As Integer = 31719
Dim Name As String = "Belize"
Dim Area As Float = 1.9
Dim Country As Country = New Country{ .Name = "Palau", ...}
因为本地变量声明的类型缺省是通过推断的,所以怎样设置选项 Strict都没有关系,对这些变量的访问通常都是前期绑定的。在VB9.0里程序员必须显式指定后期办定,即显示声明变量类型为Object,如下:
Dim Country As Object = new Country{ .Name = "Palau", ... }
要求显式的后期绑定阻止了非正常使用后期绑定,更重要的是允许对新的数据类型比如XML使用后期绑定的强大表达功能,如下面要看到的一样。这里将会有一个可选的工程级的对当前行为的切换。
在一个For...Next或者For Each...Next中的循环控制变量也可以是一个隐式类型变量,当循环控制变量被指定的时候,如For Dim I = 0 To Count或者For Each Dim C In SmallCountries,标识符定义了一个新的隐式类型本地变量,它的类型类型是通过初始化或者集合对象表达推断的,并且受限于整个循环内。在For的右边使用Dim是VB9的一个新特性,也是作为隐式类型的循环变量存在的。
通过这样的类型推断方法,我们可以重写打印所有小国家的循环:
For Each Dim Country In SmallCountries
Console.WriteLine(Country.Name)
Next
Country的类型被推断为Country,也即SmallCountries的元素类型。
对象和集合对象初始化
在Visual Basic中,With语句简化了对具有多个成员的聚合值的访问方式,从而无需多次指定目标的表达式。在一个With的语句块中,一个对成员访问的表达式通过"."来开始,这和加上With语句中的目标表达式效果是相同的,举例来说,如下的语句初始化了一个新的Country实例并且也给它的所有域赋值:
Dim Palau = New Country()
With Palau
.Name = "Palau"
.Area = 458
.Population = 16952
End With
新的VB9.0的对象初始化采用了和With相类似的表达形式来创建复杂的对象。使用对象初始化,我们可以将上面两条语句合并为一个(隐式类型)本地声明,如下:
Dim Palau = New Country { _
.Name = "Palau",_
.Area = 458, _
.Population = 16952
}
这样的表达式方式的对象初始化对查询非常重要。典型的一个查询看上去像一个在等号右边使用Select子句初始化的一个对象声明。因为Select子句返回一个表达式,那么我们必须使用一个表达式来初始化整个对象。
就像我们已经看到的那样,对象初始化对创建集合对象式的复杂对象是非常方便的,所有的集合对象都支持一个Add方法,可以通过使用集合对象初始化表达式来实现初始化。举例来说,给定下面的城市作为局部类:
Partial Class City
Public Property Name As String
Public Property Country As String
Public Property Longitude As Float
Public Property Latitude As Float
End Class
我们可以创建一个列表List(Of City)如下:
Dim Capitals = New List(Of City){ _
{ .Name = "Antanarivo", _
.Country = "Madagascar", _
.Longitude = 47.4, _
.Lattitude = -18.6 }, _
{ .Name = "Belmopan", _
.Country = "Belize", _
.Longitude = -88.5, _
.Latitude = 17.1 }, _
{ .Name = "Monaco", _
.Country = "Monaco", _
.Longtitude = 7.2, _
.Latitude = 43.7 }, _
{ .Country = "Palau", .Name = "Koror", _
.Longitude = 135, _
.Latitude = 8 } _
}
这个例子让然使用对象初始化,初始化的构造器都是从上下文推断出来的。在这里,所有的初始化都等同于全形式的NewCity{...}。
--------------------------------------------------------------------------------
匿名类型
经常我们想从一个查询结果中移开或者取出一些类型的成员。比如说我们想取处热带国家的Name和Country,使用经纬度来指定热带区域,并且在结果中突出这些值。在VB9中,我们通过创建一个对象实例--无须对类型进行命名--对每个处于南北回归线中间的城市C:
Const TropicOfCancer = 23.5
Const TropicOfCapricorn = -23.5
Dim Tropical = Select New{ .Name = City.Name, .Country = City.Country } _
From City In Capitals _
Where TropicOfCancer =< City.Latitude _
AndAlso City.Latitude >= TropicOfCapricorn
本地变量Tropical的推断类型是一个匿名类型的实例的集合,也就是IEnumerable(Of { Name As String, Country As String })。Visual Basic编译器将会创建一个新的,系统生成的类,如_Name_As_String_Country_As_String_,它的成员名和类型都是通过对象初始化推断出来的:
Class _Name_As_String_Country_As_String_
Public Property Name As String
Public Property Country As String
Public Default Property Item(Index As Integer) As Object
...
End Class
在同一个程序里,编译器将会合并相同的匿名类型。两个指定了相同名称和类型的属性顺序的匿名对象初始化将会产生相同的匿名类型实例。形式上看,VB产生的匿名类型被转化为Object,这样允许编译器一致的将匿名类型作为参数或者函数结果传递。在VB代码里,编译器会对产生的类使用特别定制的属性进行标识,以提示类型_Name_As_String_Country_As_String_实际代表了匿名类型{ Name As String, Country As String }。
因为匿名类型一般都是用来把一个已有类型中的成员提取出来,VB9.0允许使用简单的表示方法New { City.Name, City.Country }来简写长语法的New { .Name = City.Name, .Country = City.Country }。当在一个查询综合的结果表达式中使用时,我们可以进一步简化:
Dim Tropical = Select City.Name, City.Country _
From City In Capitals _
Where TropicOfCancer =< City.Latitude _
AndAlso City.Latitude >= TropicOfCapricorn
注意长表达和短表达的效果都是一样的。
深层次的XML支持
XLinq是一个新的,常驻内存的XML编程API,它主要是为了提升最新的.NET框架的语言集成的查询框架能力而设计的。就像查询综合给标准的.NET框架查询操作增加了熟悉的方便的语法一样,Visual Basic 9.0通过XML literals和late binding over XML提供了对XLinq的支持。
为了探究XML,我们同对对平面关系数据源Countries和Capitals的查询,构造一个层次的XML模型,把每个城市的首都作为一个子元素,并且计算人口密度作为属性。
为了查找给定国家的首都,我们对每个国家的name成员和每个城市的国家名进行join操作。给定一个国家和它的首都,我们可以通过使用已经计算的值填充嵌套的表达hole轻松的构建XML段。我们可以用括号括起name属性来写"hole",如Name=(Country.Name),也可以从ASP.NET中借来一种特殊的方式,如<Name><%= City.Name %></Name>。这里是我们结合XML和查询综合的查询:
Dim CountriesWithCapital As XElement = _
<Countries> <%= Select <Country Name=(Country.Name)
Density=(Country.Population/Country.Area)>
<Capital>
<Name><%= City.Name %></Name>
<Longitude><%= City.Longitude %></Longtitude>
<Latitude><%= City.Latitude %></Latitude>
</Capital>
</Country> _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country %>
</Countries>
注意XElement类型可以从声明中省略,在这里它将像其他本地声明一样进行推断。我们在这里也留下了一个显式类型,下面将解释。
在这个声明里,我们希望在<Countries>元素里Select查询的结果进行替代。因此,Select查询是第一个hole的内容,在<Countries>里使用熟悉的ASP.NET形式的标志<%=and%>来分割。因为Select查询结果是一个表达式,XML literal也是表达式,自然,Select内也可以嵌套使用Select。这样的嵌套式的使用包含了嵌套的"hole"Country.Name和计算的人口密度率Country.Population/Country.Area,还有首都的名字和坐标。
当编译运行时,上面的查询将会返回如下的XML文档:
<Countries>
<Country Name="Palau" Density="0.037117903930131008">
<Capital>
<Name>Koror</Name>
<Longitude>135</Longitude>
<Latitude>8</Latitude>
</Capital>
</Country>
<Country Name="Monaco" Density="16694.21052631579">
<Capital>
<Name>Monaco</Name>
<Longitude>7.2</Longitude>
<Latitude>3.7</Latitude>
</Capital>
</Country>
<Country Name="Belize" Density="9.5512195121951216">
<Capital>
<Name>Belmopan</Name>
<Longitude>-88.5</Longitude>
<Latitude>17.1</Latitude>
</Capital>
</Country>
<Country Name="Madagascar" Density="23.287181452711909">
<Capital>
<Name>Antananarivo</Name>
<Longitude>47.4</Longitude>
<Latitude>-18.6</Latitude>
</Capital>
</Country>
</Countries>
Visual Basic 9.0编译XML literal成System.Xml.XLinq对象,保证其他使用XLinq的语言和VB一样具有互操作性。在我们这个例子里,编译器产生的代码(如果我们可以看到的话)可能是:
Dim CountriesWithCapital As XElement = _
New XElement("Countries", _
Select New XElement("Country", _
New XAttribute("Name", Country.Name), _
New XAttribute("Density", Country.Population/Country.Area), _
New XElement("Capital", _
New XElement("Name", City.Name), _
New XElement("Longitude", City.Longitude), _
New XElement("Latitude", City.Latitude)
)
)
From Country In Countries, City In Capitals _
Where Country.Name = City.Country)
除了构造XML,Visual Basic9.0也通过XML的后期绑定简化了访问XML结构。也就是,在VB代码中的标识符是在运行时和XML属性和元素绑定的。举例来说,我们可以打印出人口密度如下:
1. 使用child axis CountriesWithCapital.Country来从CountriesWithCapital XML结构获取所有的"Country"元素;
2. 使用attribute axis Country.@Density获取Country元素的"Density"属性;
3. 使用descendants axis Country...Latitude--在源代码中写三个点,获取Country元素的所有"Latitude"子元素,无论他们在层次结构中处于多深;
4. 使用extension indexer on IEnumerable(Of T)来选择结果序列中的第一个元素。
如果放在一起,代码如下:
For Each Dim Country In CountriesWithCapital.Country
Console.WriteLine("Density = "+ Country.@Density)
Console.WriteLine("Latitude = "+ Country...Latitude(0))
Next
当一个声明,赋值或者初始化是Object类型而不是其他指定的类型的时候,编译器知道对一般的对象使用后期绑定。同时,编译器液汁到当目标表达式是XElement, XDocument, or XAttribute的类型或者集合对象时使用后期绑定。
作为后期绑定的结果,编译器将如下翻译:
·child axis表达式CountriesWithCapital.Country 翻译成raw XLinq call CountriesWithCapital.Elements("Country"),返回所有名为"Country"的Country元素的子元素的集合对象;
·attribute axis 表达式Country.@Density 翻译成Country.Attribute("Density"),返回Country的名为"Density"的单个子属性;
·descendant axis表达式Country...Latitude(0)翻译成ElementAt(Country.Descendants(Latitude),0)的结合,返回所有Country任何层次的所有元素的集合对象。
--------------------------------------------------------------------------------
查询综合
查询综合提供了一个和SQL非常类似的语言集成语法来进行查询,不但和VB融合的很好,而且和新的.NET语言集成查询框架平滑结合。
熟悉SQL实现的人就会发现,在当前的.NET框架的序列操作符中,许多关系代数的操作符如发射,选择,叉乘,分组和排序这些查询处理中的查询符号。
因为查询综合的语义是通过把它们转化为序列操作符来定义的,这些操作符将会和任何在此范围内的序列操作符绑定。这暗示了通过引进一个特殊的实现,查询综合语法可以有效地被用户重新绑定。特别的,查询综合可以和一个使用Dlinq基础结构的序列操作符实现重新绑定,或者和试图分布式的在多个本地或者远程的数据源执行查询的一个本地查询优化器绑定。这种序列操作符的重新绑定和经典的COM提供者模型在本质上类似,这样相同接口上的不同实现可以无须修改底层应用代码来支持多个操作和部署。
基本的Select...From...Where...综合过滤出所有满足Where子句中条件的值,以下是一个例子显示如何查找到人口少于100万的国家:
Dim SmallCountries = Select Country _ From Country In Countries _ Where Country.Population < 1000000
在一个序列操作符中,操作符It和当前"行"绑定。It的成员如Me是自动进入这个范围的。It的概念和XQuery的上下文项"."相关联,它可以像SQL中"*"中一样被使用。比如,我们想使用如下的查询返回所有的国家和其首都的集合对象:
Dim CountriesWithCapital = _
Select It _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
这个本地声明的推断类型是IEnumerable(Of { Country As Country, City As City }).
使用Order By子句,我们可以根据任何数量的排序关键字对查询结果排序。下面的例子中,查询语句按照它们的经度升序和人口降序的方式返回所有的国家名字列表:
Dim Sorted = Select Country.Name _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
Order By City.Longtitude Asc, Country.Population Desc
集合操作符如Min, Max, Count, Avg, Sum...操作集合对象并"聚合"它们成一个单值。我们可以计算所有的小国家:
Dim N As Integer = _
Select Count(Country) _
From Country In Countries _
Where Country.Population < 1000000
像SQL一样,我们提供了特殊的聚合语法,这对同时进行多个聚合操作是非常方便的。比如,我们可以使用一条语句,统计所有的小国家数目,并且计算它们的平均人口密度:
Dim R As { Total As Integer, Density As Double } = _
Select New { .Total = Count(Country), _
.Density = Avg(Country.Population/Country.Area) } _
From Country In Countries _
Where Country.Population < 1000000
这种形式的聚合对应用一个编译器产生的聚合函数对没有进行聚合的自然结果集合的结果是非常方便的。 聚合函数一般经常出现在分割源集合对象时使用。举例来说,我们可以根据是否处于热带来对所有国家进行分组,然后统计每个组的数目。为了这样做,我们引进一下Group By子句。IsTropical是对判断国家是否处于热带的一个封装函数:
Partial Class Country
Function IsTropical() As Boolean
Return TropicOfCancer =< Me.Latitude _
AndAlso Me.Latitude >= TropicOfCapricorn
End Function
End Class
通过这个函数,我们可以使用和上面一样的聚合,但是首先得按照Country.IsTropical得结果是否一样来对输入得Country和Capital对的集合对象进行分割。在这个例子里有两个这样的分类:一个包含热带国家帕劳群岛,伯利兹城和马达加斯加岛,另一个包含非热带国家摩纳哥。
关键字 国家 城市
Country.IsTropical() = True Palau Koror
Country.IsTropical() = True Belize Belmopan
Country.IsTropical() = True Madagascar Antanarivo
Country.IsTropical() = False Monaco Monaco
然后,我们通过计算总数和平均密度来对这些组进行值的聚合。返回类型是一个对Total As Integer和 Density As Double的集合对象:
Dim CountriesByClimate _
As IEnumerable(Of Total As Integer, Density As Double }) = Select New { .Total = Count(Country), _
.Density = Avg(Country.Population/Country.Area) } _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
Group By Country.IsTropical()
以上的查询隐藏了相当复杂的操作,因为Group By子句的结果实际上是一个组值的集合对象,其类型是IEnumerable(Of Grouping(Of { Boolean, { Country As Country, City As City })),和上面讨论的很相象。每个这样的Grouping项包含一个从key提取表达式Country.IsTropical()衍生的Key成员和一个包含单个key提取表达式值相同的国家和城市的集合对象。VB编译器合成用户定义的聚合函数,即给定一个组,通过聚合每个分割来计算所要的结果。
注意前面的例子里每个Group同时包含Country和Capital,我们只需要Country来计算最后的查询结果。Group By子句允许这些组的一个预先选择。比如,我们可以根据国家的气候对国家名进行分类:
Dim ByHemisphere As IEnumerable(Of Grouping(Of Boolean, String)) = _
Select It _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
Group Country.Name By City.Latitude >= 0
这会返回集合对象{ New Grouping { .Key = False, .Group = { "Madagascar", "Belize" }}, New Grouping { .Key = True, .Group = { "Palau" }}。
查询综合在Visual Basic 8.0里是完全组合式的,意思就是说查询综合可以任意的嵌套。组合特性使得通过每个孤立的子表达式来理解大的查询变得很容易。组合特性也使得定义语义和语言的规则变得更加容易。组合特性,作为一个设计原则,和SQL的设计原则有所不同。SQL语言不是完全组合的,程序员还必须通过日积月累的社区里的经验积累来处理很多个例。由于却反完全的组合特性,所以通过个体片断来理解一个复杂的SQL查询总体上来说是不可能的。
SQL缺乏组合特性的原因是关系数据模型就不是组合式的。比如,表可能不包含子表;也就是说,所有的表都必须是平面的。结果,SQL程序员只能写单片的结果为平面表的表达式来适应SQL的数据模型,而不是将大的表达式分解为小的。引用Jim Gray的话来说"计算机科学里任何不是递归的东西都不是好的"因为Visual Basic是基于CLR类型系统,并没有限制哪种类型作为其他类型的组件出现。除去静态类型规则,并没有限制表达式作为其他类型的组件出现。因此,不但是行,对象,XML,还有活动目录,文件,注册表项等等都是查询源和查询结果的"一等公民"。
--------------------------------------------------------------------------------
查询综合
查询综合提供了一个和SQL非常类似的语言集成语法来进行查询,不但和VB融合的很好,而且和新的.NET语言集成查询框架平滑结合。
熟悉SQL实现的人就会发现,在当前的.NET框架的序列操作符中,许多关系代数的操作符如发射,选择,叉乘,分组和排序这些查询处理中的查询符号。
因为查询综合的语义是通过把它们转化为序列操作符来定义的,这些操作符将会和任何在此范围内的序列操作符绑定。这暗示了通过引进一个特殊的实现,查询综合语法可以有效地被用户重新绑定。特别的,查询综合可以和一个使用Dlinq基础结构的序列操作符实现重新绑定,或者和试图分布式的在多个本地或者远程的数据源执行查询的一个本地查询优化器绑定。这种序列操作符的重新绑定和经典的COM提供者模型在本质上类似,这样相同接口上的不同实现可以无须修改底层应用代码来支持多个操作和部署。
基本的Select...From...Where...综合过滤出所有满足Where子句中条件的值,以下是一个例子显示如何查找到人口少于100万的国家:
Dim SmallCountries = Select Country _ From Country In Countries _ Where Country.Population < 1000000
在一个序列操作符中,操作符It和当前"行"绑定。It的成员如Me是自动进入这个范围的。It的概念和XQuery的上下文项"."相关联,它可以像SQL中"*"中一样被使用。比如,我们想使用如下的查询返回所有的国家和其首都的集合对象:
Dim CountriesWithCapital = _
Select It _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
这个本地声明的推断类型是IEnumerable(Of { Country As Country, City As City }).
使用Order By子句,我们可以根据任何数量的排序关键字对查询结果排序。下面的例子中,查询语句按照它们的经度升序和人口降序的方式返回所有的国家名字列表:
Dim Sorted = Select Country.Name _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
Order By City.Longtitude Asc, Country.Population Desc
集合操作符如Min, Max, Count, Avg, Sum...操作集合对象并"聚合"它们成一个单值。我们可以计算所有的小国家:
Dim N As Integer = _
Select Count(Country) _
From Country In Countries _
Where Country.Population < 1000000
像SQL一样,我们提供了特殊的聚合语法,这对同时进行多个聚合操作是非常方便的。比如,我们可以使用一条语句,统计所有的小国家数目,并且计算它们的平均人口密度:
Dim R As { Total As Integer, Density As Double } = _
Select New { .Total = Count(Country), _
.Density = Avg(Country.Population/Country.Area) } _
From Country In Countries _
Where Country.Population < 1000000
这种形式的聚合对应用一个编译器产生的聚合函数对没有进行聚合的自然结果集合的结果是非常方便的。 聚合函数一般经常出现在分割源集合对象时使用。举例来说,我们可以根据是否处于热带来对所有国家进行分组,然后统计每个组的数目。为了这样做,我们引进一下Group By子句。IsTropical是对判断国家是否处于热带的一个封装函数:
Partial Class Country
Function IsTropical() As Boolean
Return TropicOfCancer =< Me.Latitude _
AndAlso Me.Latitude >= TropicOfCapricorn
End Function
End Class
通过这个函数,我们可以使用和上面一样的聚合,但是首先得按照Country.IsTropical得结果是否一样来对输入得Country和Capital对的集合对象进行分割。在这个例子里有两个这样的分类:一个包含热带国家帕劳群岛,伯利兹城和马达加斯加岛,另一个包含非热带国家摩纳哥。
关键字 国家 城市
Country.IsTropical() = True Palau Koror
Country.IsTropical() = True Belize Belmopan
Country.IsTropical() = True Madagascar Antanarivo
Country.IsTropical() = False Monaco Monaco
然后,我们通过计算总数和平均密度来对这些组进行值的聚合。返回类型是一个对Total As Integer和 Density As Double的集合对象:
Dim CountriesByClimate _
As IEnumerable(Of Total As Integer, Density As Double }) = Select New { .Total = Count(Country), _
.Density = Avg(Country.Population/Country.Area) } _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
Group By Country.IsTropical()
以上的查询隐藏了相当复杂的操作,因为Group By子句的结果实际上是一个组值的集合对象,其类型是IEnumerable(Of Grouping(Of { Boolean, { Country As Country, City As City })),和上面讨论的很相象。每个这样的Grouping项包含一个从key提取表达式Country.IsTropical()衍生的Key成员和一个包含单个key提取表达式值相同的国家和城市的集合对象。VB编译器合成用户定义的聚合函数,即给定一个组,通过聚合每个分割来计算所要的结果。
注意前面的例子里每个Group同时包含Country和Capital,我们只需要Country来计算最后的查询结果。Group By子句允许这些组的一个预先选择。比如,我们可以根据国家的气候对国家名进行分类:
Dim ByHemisphere As IEnumerable(Of Grouping(Of Boolean, String)) = _
Select It _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
Group Country.Name By City.Latitude >= 0
这会返回集合对象{ New Grouping { .Key = False, .Group = { "Madagascar", "Belize" }}, New Grouping { .Key = True, .Group = { "Palau" }}。
查询综合在Visual Basic 8.0里是完全组合式的,意思就是说查询综合可以任意的嵌套。组合特性使得通过每个孤立的子表达式来理解大的查询变得很容易。组合特性也使得定义语义和语言的规则变得更加容易。组合特性,作为一个设计原则,和SQL的设计原则有所不同。SQL语言不是完全组合的,程序员还必须通过日积月累的社区里的经验积累来处理很多个例。由于却反完全的组合特性,所以通过个体片断来理解一个复杂的SQL查询总体上来说是不可能的。
SQL缺乏组合特性的原因是关系数据模型就不是组合式的。比如,表可能不包含子表;也就是说,所有的表都必须是平面的。结果,SQL程序员只能写单片的结果为平面表的表达式来适应SQL的数据模型,而不是将大的表达式分解为小的。引用Jim Gray的话来说"计算机科学里任何不是递归的东西都不是好的"因为Visual Basic是基于CLR类型系统,并没有限制哪种类型作为其他类型的组件出现。除去静态类型规则,并没有限制表达式作为其他类型的组件出现。因此,不但是行,对象,XML,还有活动目录,文件,注册表项等等都是查询源和查询结果的"一等公民"。
--------------------------------------------------------------------------------
空类型
关系数据库引入了空值的语义,空值和当前的普通程序设计语言经常不一致,程序员也不熟悉它。在以数据为中心的程序中,正确清晰地理解这些语义是很重要的。考虑到这一点,在"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成为世界上最好的编程系统。我们希望你的灵感也被激发,并且和我们一样认识到着确实是一个更加伟大的事情的开始。