简介
Visual Basic 一直以生成实用的、面向数据的业务线应用程序为中心。尽管迁移到 .NET 为应用程序开发人员带来了统一的框架和托管平台,但是下一版本的 Visual Basic 包括一组对开发人员构建面向数据的应用程序时的工作效率影响深远的功能。这些语言扩展引入了适用于所有数据源的通用查询工具,不管是关系对象图、分层对象图还是 XML 文档。
本文档将简略介绍这些新增功能。 Visual Basic 9.0 入门
要查看这些语言功能的实际作用,让我们首先看一个真实的示例 - CIA World Factbook 数据库。该数据库包含有关世界各国的各种地理、经济、社会和政治信息。为了演示示例,我们从每个国家/地区的名称及其首都/首府、总面积和总人口的架构开始。通过使用以下类(为简便起见,使用伪代码),在 Visual Basic 9.0 中表示此架构: Class Country Public Property Name As String Public Property Area As Long Public Property Population As Integer End Class
下面是一个将用作运行示例的国家/地区数据库的小子集: Dim countries = { New Country With { .Name = "Palau", .Area = 458, .Population = 16952 }, _ New Country With { .Name = "Monaco", .Area = 1.9, .Population = 31719 }, _ New Country With { .Name = "Belize", .Area = 22960, .Population = 219296 }, _ New Country With { .Name = "Madagascar", .Area = 587040, .Population = 13670507}}
有了此列表,可以使用以下查询表达式来查询其人口少于一百万的所有国家/地区: Dim smallCountries = From country In countries _ Where country.Population < 1000000 _ Select country For Each country In SmallCountries Console.WriteLine(country.Name) Next
由于只有马达加斯加的居住人口超过一百万,因此在编译和运行后,上述程序将打印出下列国家/地区名称: Palau Monaco Belize
让我们检查一下该程序,了解使得编写此程序如此简单的 Visual Basic 9.0 功能。首先,表示 Countries 的每个表达式的声明都使用新增的“对象初始值设定项”语法 New Country With {..., .Area = 458, ...},通过类似于现在 With 语句的、基于表达式的简明语法创建复杂的对象实例。
该声明还说明了“隐式类型的局部变量”声明,其中编译器通过声明右侧的初始值设定项表达式来推断局部变量 Countries 的类型。上述声明完全等效于 Country() 类型的显式类型局部变量声明。 Dim countries As Country() = {...}
重申一下,这仍然是强类型声明;编译器已自动推断局部声明右侧的类型,程序员无需在程序中手动输入该类型。
使用 SQL 样式的查询表达式初始化局部变量声明 SmallCountries,筛选出居住人口少于一百万的所有国家/地区。查询表达式与 SQL 类似是有意的,这样已经了解 SQL 的程序员就可以更快地了解 Visual Basic 查询语法。 Dim smallCountries = From country In Countries _ Where country.Population < 1000000 _ Select country
请注意,此代码示例表示另一个隐式类型的应用程序:编译器根据查询表达式的结果类型,将 SmallCountries 的类型推断为 IEnumerable(Of Country)。编译器将查询表达式本身转换为对启用 LINQ 的 API(它为实现 IEnumerable(Of T) 的所有类型实现查询运算符)的调用。在这种情况下,转换就像下面一样简单: Dim smallCountries As IEnumerable(Of Country) = _ Countries.Where(Function(country) country.Population < 1000000). _ Select(Function(country) country)
扩展语法取决于“lambda 表达式”,后者表示返回表达式结果的内联函数。lambda 表达式被转换为委托并传递到扩展函数 Where,该函数在标准查询运算符库中被定义为 IEnumerable(Of T) 接口的扩展。
我们已了解 Visual Basic 9.0 的几项新增功能,接下来让我们深入了解更多内容。 隐式类型的局部变量
在隐式类型的局部变量声明中,局部变量的类型是通过局部声明语句右侧的初始值设定项表达式推断的。例如,编译器推断以下所有变量声明的类型: Dim population = 31719 Dim name = "Belize" Dim area = 1.9 Dim country = New Country With { .Name = "Palau", ...}
因此,它们完全等效于以下显式类型声明: Dim population As Integer = 31719 Dim name As String = "Belize" Dim area As Float = 1.9 Dim country As Country = New Country With { .Name = "Palau", ...}
由于局部变量声明的类型是通过新增的 Option Infer On(新项目的默认值)推断的,因此不管 Option Strict 的设置如何,对此类变量的访问始终是早期绑定的。程序员必须在 Visual Basic 9.0 中显式指定后期绑定,方法是将变量显式声明为 Object 类型,如下所示: Dim country As Object = New Country With { .Name = "Palau", ... }
推断类型可防止意外使用后期绑定,更重要的是,它允许为新数据类型(如 XML)绑定强大扩展,如下所示。
For...Next 或 For Each...Next 语句中的循环控制变量也可以是隐式类型的变量。指定循环控制变量时(如 For I = 0 To SmallCountries.Count 或 For Each country In smallCountries 中所示),标识符定义一个新的隐式类型局部变量,其类型通过初始值设定项或集合表达式推断且作用于整个循环。通过应用此类型推断,可以重新编写打印所有小国家/地区的循环,如下所示: For Each country In smallCountries Console.WriteLine(country.Name) Next
country 的类型被推断为 Country,即 SmallCountries 的元素类型。 对象和数组初始值设定项
在 Visual Basic 中,With 语句简化了对一个聚合值的多个成员的访问,而无须多次指定目标表达式。在 With 语句块内,对以句点开头的成员访问表达式进行计算,好像句点前面有 With 语句的目标表达式。例如,以下语句初始化新的 Country 实例,随后将其字段初始化为所需的值: Dim palau As New Country() With palau .Name = "Palau" .Area = 458 .Population = 16952 End With
Visual Basic 9.0 中新增的“对象初始值设定项”是基于表达式的 With 语句,用于简明地创建复杂的对象实例。使用对象初始值设定项,可以将上述两条语句捕获到单个(隐式类型的)局部声明中,如下所示: Dim palau = New Country With { _ .Name = "Palau", _ .Area = 458, _ .Population = 16952 _ }
表达式中此样式的对象初始化对查询是很重要的。通常,查询类似于由等号右侧的 Select 子句初始化的对象声明。由于 Select 子句返回表达式,因此必须能够用单个表达式初始化整个对象。
正如我们所看到的那样,对象初始值设定项还便于为复杂对象创建集合。通过使用“数组初始值设定项”表达式,可以初始化数组和推断元素类型。例如,假定将城市的声明作为类, Class City Public Property Name As String Public Property Country As String Public Property Longitude As Long Public Property Latitude As Long End Class
则可以为示例国家/地区创建首都/首府数组,如下所示: Dim Capitals = { _ New City With { _ .Name = "Antanarivo", _ .Country = "Madagascar", _ .Longitude = 47.4, _ .Latitude = -18.6 }, _ New City With { _ .Name = "Belmopan", _ .Country = "Belize", _ .Longitude = -88.5, _ .Latitude = 17.1 }, _ New City With { _ .Name = "Monaco", _ .Country = "Monaco", _ .Longitude = 7.2, _ .Latitude = 43.7 }, _ New City With { _ .Country = "Palau", .Name = "Koror", _ .Longitude = 135, _ .Latitude = 8 } _ }
匿名类型
通常,我们希望删除或剔除一个类型的某些成员,从而得到查询结果。例如,我们可能只希望知道所有位于热带的首都/首府的 Name 和 Country,那么就使用源数据中的 Latitude 和 Longitude 列标识热带,而在结果中剔除其他列。在 Visual Basic 9.0 中,我们通过为其纬度位于北回归线和南回归线之间的每个城市 C 创建新的对象实例(而无须指定类型)来做到这一点: Const TropicOfCancer = 23.5 Const TropicOfCapricorn = -23.5 Dim tropical = From city In Capitals _ Where TropicOfCancer <= city.Latitude _ AndAlso city.Latitude >= TropicOfCapricorn _ Select New With {city.Name, city.Country}
局部变量 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 ... End Class
在同一程序内,编译器将合并相同的匿名类型。按相同顺序指定相同名称和类型的属性序列的两个匿名对象初始值设定项,将生成相同匿名类型的实例。在外部,Visual Basic 生成的匿名类型被替换为 Object,这样编译器就可以将匿名类型作为函数参数和结果统一进行传递。
由于匿名类型通常用于现有类型的项目成员,因此 Visual Basic 9.0 允许使用简略计划表示法 New With { city.Name, city.Country } 缩写长格式的 New With { .Name = city.Name, .Country = city.Country }。如果在查询理解的结果表达式中使用,则可以进一步缩写计划初始值设定项,如下所示: Dim Tropical = From city In Capitals _ Where TropicOfCancer <= city.Latitude _ AndAlso city.Latitude >= TropicOfCapricorn _ Select city.Name, city.Country
请注意,这两种缩写格式在意义上与上述长格式完全相同。 深入的 XML 支持
“LINQ to XML”是新增的内存中 XML 编程 API,专为利用最新的 .NET Framework 功能(如语言集成查询框架)而设计。正如查询理解通过基础的标准 .NET Framework 查询运算符添加熟悉、便利的语法一样,Visual Basic 9.0 通过 “XML 文字”和“XML 属性”提供对 LINQ to XML 的深入支持。
为了说明 XML 文字,让我们通过实际上为平面关系数据源的 Countries 和 Capitals 进行查询,从而构造分层 XML 模型,该模型将嵌套每个国家/地区的首都/首府(作为子元素)并计算人口密度(作为属性)。
为找出给定国家/地区的首都/首府,我们对每个国家/地区的名称成员与每个城市的国家/地区成员执行“联接”操作。如果给定了某个国家/地区及其首都/首府,则可以通过将计算值填入嵌入式表达式“空洞”轻松构造 XML 片段。我们将使用与 ASP 类似的语法为 Visual Basic 表达式编写“空洞”,如 Name=<%= country.Name %> 或 <Name><%= city.Name %></Name> 中所示。下面是将 XML 文字和查询理解组合在一起的查询: Dim countriesWithCapital As XElement = _ <Countries> <%= From country In Countries, city In Capitals _ Where country.Name = city.Country _ Select <Country Name=<%= country.Name %> Density=<%= country.Population / country.Area %>> <Capital> <Name><%= city.Name %></Name> <Longitude><%= city.Longitude %></Longitude> <Latitude><%= city.Latitude %></Latitude> </Capital> </Country> _ %> </Countries>
请注意,可以从声明中省略类型 XElement,在这种情况下将对它进行推断,就像任何其他局部声明一样。
在此声明中,要在 <Countries> 元素替换 Select 查询的结果。 因此,Select 查询是第一个“空洞”的内容,由 <Countries> 内常见的 ASP 样式标记 <%= 和 %> 进行界定。由于 Select 查询的结果是一个表达式,而且 XML 文字是表达式,因此在 Select 自身中嵌套另一个 XML 文字是很自然的。此嵌套文字本身包含 Country.Name 的嵌套属性“空洞”和计算的人口密度比率 Country.Population/Country.Area,以及首都/首府名称和坐标的嵌套元素“空洞”。
在编译和运行后,上述查询将返回以下 XML 文档(为节省篇幅,对标准 IDE 输出的格式稍作调整) <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 文字编译为常规 System.Xml.Linq 对象,从而确保 Visual Basic 与使用 LINQ to XML 的其他语言之间的完全互操作性。在我们的示例查询中,编译器生成的代码(如果可以看到它)所示如下: Dim countriesWithCapital As XElement = _ New XElement("Countries", _ From country In Countries, city In Capitals _ Where country.Name = city.Country _ 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))))
除构造 XML 外,Visual Basic 9.0 还通过 XML 属性简化了对 XML 结构的访问;即,在运行时 Visual Basic 代码中的标识符将被绑定到对应的 XML 属性和元素。例如,可以打印所有示例国家/地区的人口密度,如下所示:
• |
使用“子轴”countriesWithCapital.<Country> 获取 countriesWithCapital XML 结构中的所有“Country”元素。 |
• |
使用“属性轴”country.@Density 获取 Country 元素的“Density”属性。 |
• |
使用“子代轴”country...<Latitude>(在源代码中为三个点)获取 Country 元素的所有“Latitude”子元素,而不管它们在层次结构中的深度如何。 |
• |
使用 IEnumerable(Of XElement) 上的“扩展属性”.Value 选择生成序列的第一个元素的值,或者使用扩展索引器 (i) 选择第 i 个元素。 |
组合使用所有这些功能,可以大幅度地压缩和简化代码: For Each country In countriesWithCapital.<Country> Console.WriteLine("Density = " & country.@Density) Console.WriteLine("Latitude = " & country...<Latitude>.Value) Next
当声明、赋值或初始化的目标表达式为 Object 类型而不是某些更具体的类型时,编译器知道针对常规对象使用后期绑定。同样,当目标表达式属于 XElement、XDocument 或 XAttribute 类型或集合时,编译器知道针对 XML 使用绑定。
针对 XML 使用后期绑定后,编译器将进行如下转换:
• |
子轴表达式 countriesWithCapital.<Country> 转换为原始的 LINQ to XML 调用 countriesWithCapital.Elements("Country")(它返回 Country 元素中名为“Country”的所有子元素的集合)。 |
• |
属性轴表达式 country.@Density 转换为 Country.Attribute("Density").Value(它返回 Country 中名为“Density”的单个子属性)。 |
• |
子代轴表达式 country...<Latitude> 转换为原始的 LINQ to XML 调用 country.Descendants(“Latitude”)(它返回在 country 下任何深度上命名的所有元素的集合)。 | 查询理解
“查询运算符”是可以一次应用于整个集合中一组值的运算符,如 Select、Order By 或 Where。“查询表达式”是将一系列查询运算符应用于特定集合的表达式。例如,以下查询表达式搜索一个国家/地区集合,返回居住人口少于一百万的所有国家/地区的名称: Dim smallCountries = From country In Countries _ Where country.Population < 1000000 _ Select country
查询表达式语法设计得与标准关系 SQL 语法非常接近,旨在使熟悉 SQL 的任何人无需接受多少指导就能够使用查询表达式。但是,该语法不受 SQL 的约束,查询表达式也不用于将 SQL 转换为 Visual Basic。由于 SQL 是根据纯关系模型设计的,因此它的一些语言规则不能很好地在允许(甚至包含)层次结构的类型系统上运行。此外,一些 SQL 语法和语义元素与现有的 Visual Basic 语法或语义相冲突或者配合不好。因此,虽然熟悉 SQL 的人都应熟悉查询表达式,但是仍存在需要了解的差异。
查询表达式被转换为对基础序列运算符(位于 From 子句中被指定为源类型的可查询类型上)的调用。如果序列运算符通常被定义为源类型上的“扩展方法”,则将它们绑定到处于作用域中的任何序列运算符。这意味着,通过导入特定的实现,可以将查询表达式语法再次绑定到启用 LINQ 的不同 API。这就是将查询表达式再次绑定到使用 LINQ to SQL 或 LINQ to objects(在内存中执行查询的局部查询执行引擎)的实现的方法。
有些查询运算符(如 From、Select 和 Group By)引入了名为“范围变量”的特殊类型的局部变量。默认情况下,范围变量的作用范围是,从引入运算符到隐藏范围变量并表示查询计算时集合中单个行的属性或列的运算符。例如,在以下查询中: Dim smallCountries = From country In Countries _ Where country.Population < 1000000 _ Select country
From 运算符引入了类型为“Country”的范围变量 country。接下来的 Where 查询运算符则引用范围变量 country,来表示筛选表达式 country.Population < 1000000 中的各个单独客户。
有些查询运算符(如 Distinct)不使用或更改控制变量。其他查询运算符(如 Select)隐藏作用域中的当前范围变量,并引入新的范围变量。例如,在以下查询中: Dim smallCountries = From country In Countries _ Select country.Name, Pop = country.Population Order By Pop
Order By 查询运算符只能访问由 Select 运算符引入的 Name 和 Pop 范围变量;如果 Order By 运算符尝试引用 Country,则会出现编译时错误。
如果查询不以 Select 运算符结尾,则该集合得到的元素类型就像在查询范围内存在一个包含所有控制变量的 Select 计划一样: Dim countriesWithCapital = From country In Countries, city In Capitals _ Where country.Name = city.Country The inferred type for this local declaration is (in pseudo-code to represent an anonymous type) IEnumerable(Of { Country As Country, City As City }). 查询表达式与 SQL 的不同之处:组合性和分层数据
Visual Basic 9.0 中的“查询表达式”是完全“组合的”,这意味着通过追加具有其他查询运算符的查询可以随意嵌入或构造查询理解。由于其组合性,只需分别了解各个单独的子表达式即可轻松了解大型查询,而且可以轻松地跟踪流过每个查询运算符的语义和类型。但是,使用以组合性作为设计原则的表达式编写查询的体验与使用 SQL(将查询分析为单个块)有很大不同。
此外,启用 LINQ 的 API 用于实现具有“延迟执行”的序列运算符。延迟执行意味着在枚举结果之前不计算查询。对于 LINQ to SQL,这意味着在请求结果之前不会将查询远程传输到 SQL。这意味着,将查询分离到多条语句中并不表示会多次找到数据库。因此,在 SQL 中通常是嵌套查询,而在 LINQ 中就变成了组合查询。
SQL 缺少组合性的一个原因是,基础关系数据模型本身不是组合的。例如,表不能包含子表;换句话说,所有表都必须是平面的。因此,SQL 程序员编写其结果为平面表、适合于 SQL 数据模型的单一表达式,而不是将复杂表达式分解为更小的单元。由于 Visual Basic 基于 CLR 类型系统,因此没有限制哪些类型可以作为其他类型的组件出现。除了静态类型规则外,对可以作为其他表达式的组件出现的表达式类型没有限制。因此,不仅行、对象和 XML,而且 Active Directory、文件、注册表项等在查询源和查询结果中都是一流成员。 查询运算符
熟悉 SQL 实现的那些人将意识到,在基础 .NET 序列运算符中,许多组合性关系代数运算符(如计划、选择、交叉积、分组和排序)都表示查询处理器中的查询计划。
• |
From 运算符引入一个或多个范围变量,并指定要查询的集合或计算范围变量的值。 |
• |
Select 运算符指定输出集合的形状。 |
• |
Where 和 Distinct 运算符限制集合的值。 |
• |
Order By 运算符要求对集合排序。 |
• |
Skip、Skip While、Take 和 Take While 运算符根据顺序或条件返回集合的子集。 |
• |
Union、Union All、Except 和 Intersect 运算符对两个集合进行操作,然后生成一个集合。 |
• |
Group By 运算符根据一个或多个关键字对集合进行分组。 |
• |
Avg、Sum、Count、Min 和 Max 运算符聚合集合并生成值。 |
• |
Any 和 All 运算符根据条件聚合集合并返回布尔值。 |
• |
Join 运算符对两个集合进行操作,并根据从元素派生的匹配项生成一个集合。 |
• |
Group Join 运算符根据从元素提取的匹配项将两个集合联接在一起。 |
可以在完整的语言规范中找到所有运算符的完整语法。但是,为了便于说明,下面将查找每个国家/地区的首都/首府,并按首都/首府所在纬度对国家/地区名称进行排序: Dim countriesWithCapital = _ From country In Countries _ Join city In Capitals On country.Name Equals city.Country _ Order By city.Latitude _ Select country.Name
对于根据集合计算标量值的查询,Aggregate 运算符将遍历集合。以下查询在一条语句中查找小国家/地区的数量并计算其平均人口密度: Dim popInfo = _ Aggregate country In Countries _ Where country.Population < 1000000 _ Into Total = Count(), Density = Average(country.Population/country.Area)
聚合函数最常用于组合分区的源集合。例如,可以根据是否位于热带对所有国家/地区进行分组,然后聚合每个组的计数。为此,可以将聚合运算符与 Group By 和 Group Join 子句联合使用。在下面的示例中,Helper 函数 IsTropical 将封装测试 City 是否为热带气候: Function IsTropical() As Boolean Return TropicOfCancer =< Me.Latitude AndAlso Me.Latitude >= TropicOfCapricorn End Function
考虑到此 Helper 函数,使用与上面完全相同的聚合,但是首先将 Country 和 Capital 对的输入集合分区到其 Country.IsTropical 相同的组中。在这种情况下,有两个这样的组:一个组包含热带国家/地区帕劳、伯利兹和马达加斯加;另一个组包含非热带国家/地区摩纳哥。
关键字 |
国家/地区 |
城市 |
Country.IsTropical() = True |
帕劳
伯利兹
马达加斯加 |
科罗尔
贝尔莫潘
塔那那利佛 |
Country.IsTropical() = False |
摩纳哥 |
摩纳哥 |
然后,通过计算总数和平均密度,聚合这些组中的值。现在,结果类型是 Total As Integer 和 Density As Double 对的集合: Dim countriesByClimate = _ From country In Countries _ Join city In Capitals On country.Name Equals city.Country _ Group By country.IsTropical() Into Total = Count(), Density = Average(country.Population/country.Area)
上述查询未充分体现其复杂性。下面的查询通过“lambda 表达式”和“扩展方法”用“方法调用语法”表示查询可以获得相同的结果。 Dim countriesByClimate7 = _ countries. _ SelectMany( _ Function(country) Capitals, _ Function(country, city) New With {country, city}). _ Where(Function(it) it.country.Name = it.city.Country). _ GroupBy( _ Function(it) it.city.IsTropical(), _ Function(IsTropical, Group) _ New With { _ IsTropical, _ .Total = Group.Count(), _ .Density = Group.Average( _ Function(it) it.country.Population / it.country.Area _ ) _ } _ )
扩展方法和 Lambda 表达式
.NET Framework 标准查询基础结构中的许多基础功能来自“扩展方法”和“lambda 表达式”。扩展方法是标记有自定义属性(允许通过实例方法语法调用它们)的共享方法。大多数的扩展方法具有类似的签名。第一个参数是对应用方法的实例,第二个参数是要应用的谓词。例如,从 Where 子句转换来的 Where 方法具有以下签名: Module IEnumerableExtensions <Extension> _ Function Where (Of TSource) _ (Source As IEnumerable(Of TSource), _ predicate As Func(Of TSource, Boolean)) As IEnumerable(Of TSource) ... End Function End Module
由于许多标准查询运算符(如 Where、Select、SelectMany 等)被定义为将 Func(Of S,T) 类型的委托视为参数的扩展方法,因此编译器不再要求生成表示谓词的委托。编译器创建“结束对象”(捕获其周围上下文的委托),并将它们传递到基础方法调用。例如,通过以下查询,编译器生成表示要传递到 Select 和 Where 函数的委托的 lambda 表达式: Dim smallCountries = From country In countries _ Where country.Population < 1000000 _ Select country.Name
编译器生成分别用于计划和谓词的两个 lambda 表达式: Function(Country As Country) country.Name Function (Country As Country) country.Population < 1000000
上述查询被转换为对标准查询框架的方法调用,将要应用的源和 lambda 表达式作为参数传递: Dim smallCountries = _ Enumerable.Select( _ Enumerable.Where(countries, _ Function (country As Country) country.Population < 1000000), _ Function(country As Country) country.Name)
可为空的类型
关系数据库为通常与普通编程语言不一致而且程序员通常不熟悉的可为空的值提供语义。在数据密集型应用程序中,程序明确无误地处理这些语义是很重要的。意识到此必要性,在 .NET Frameworks 2.0 中 CLR 使用泛型类型 Nullable(Of T As Structure) 添加了对可为空的运行时支持。使用此类型,可以声明值类型(如 Integer、Boolean、Date 等)的为空版本。由于显而易见的原因,为空类型的 Visual Basic 语法为 T?。
例如,由于并非所有国家/地区都是独立的,因此可以将新成员添加到表示其独立日期的类 Country 中(如果适用): Partial Class Country Public Property Independence As Date? End Class
帕劳的独立日期为 #10/1/1994#,而英属维尔京群岛是属于英国的领土,因此其独立日期为 Nothing。 Dim palau = _ New Country With { _ .Name = "Palau", _ .Area = 458, _ .Population = 16952, _ .Independence = #10/1/1994# } Dim virginIslands = _ New Country With { _ .Name = "Virgin Islands", _ .Area = 150, _ .Population = 13195, _ .Independence = Nothing }
Visual Basic 9.0 将支持为空值进行三值逻辑和 Null 传播算术,这意味着如果算术、比较、逻辑或按位、移位、字符串或类型运算的操作数之一为 Nothing,则结果将为 Nothing。如果这两个操作数都有适当的值,则对操作数的基础值执行运算,并将结果转换成为空值。
由于 Palau.Independence 和 VirginIslands.Independence 都具有类型 Date?,因此编译器将对下面的减法使用空传播算术,这样局部声明 PLength 和 VILength 的推断类型都将为 TimeSpan?。 Dim pLength = #8/24/2005# - Palau.Independence ‘ 3980.00:00:00
由于两个操作数都不为 Nothing,因此 PLength 的值是 3980.00:00:00。另一方面,由于 VirginIslands.Independence 的值是 Nothing,因此结果同样属于 TimeSpan? 类型,而由于空传播,VILength 的值将是 Nothing。 Dim vILength = #8/24/2005# - virginIslands.Independence ‘ 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。 宽松委托
在 Visual Basic 8.0 中使用 AddressOf 或 Handles 创建委托时,专用于绑定到委托标识符的方法之一必须与委托类型的签名完全匹配。在下面的示例中,OnClick 子例程的签名必须与 Button 类型中在后台声明的事件处理程序委托 Delegate Sub EventHandler(sender As Object, e As EventArgs) 的签名完全匹配: Dim WithEvents btn As New Button() Sub OnClick(sender As Object, e As EventArgs) Handles B.Click MessageBox.Show("Hello World from" & btn.Text) End Sub
但是,调用“非委托”函数和子例程时,Visual Basic 不要求实参与所尝试调用的方法之一完全匹配。如以下片段所示,实际上可以使用 Button 类型和 MouseEventArgs 类型(它们分别是形参 Object 和 EventArgs 的子类型)的实参调用 OnClick 子例程。 Dim m As New MouseEventArgs(MouseButtons.Left, 2, 47, 11,0) OnClick(btn, m)
相反,假定可以定义一个使用两个 Object 参数的子例程 RelaxedOnClick,则可以用 Object 和 EventArgs 类型的实参调用它: Sub RelaxedOnClick(sender As Object, e As Object) Handles btn.Click MessageBox.Show("Hello World from" & btn.Text)) End Sub Dim e As EventArgs = m Dim s As Object = btn RelaxedOnClick(btn,e)
在 Visual Basic 9.0 中,对绑定到委托进行了放宽,以便与方法调用一致。即,如果可以用与形参完全匹配的实参“调用”函数或子例程并返回委托的类型,则可以将该函数或子例程绑定到委托。换句话说,委托的绑定和定义将遵循与方法调用相同的重载决策逻辑。
这意味着,在 Visual Basic 9.0 中现在可以将使用两个 Object 参数的子例程 RelaxedOnClick 绑定到 Button 的 Click 事件: Sub RelaxedOnClick(sender As Object, e As Object) Handles btn.Click MessageBox.Show(("Hello World from" & btn.Text) End Sub
事件处理程序的两个参数 sender 和 EventArgs 几乎是无关紧要的。相反,处理程序会访问在其上直接注册事件的控件的状态,并忽略它的两个参数。为支持此常见情况,在不产生歧义的前提下,可以允许委托不带任何参数。换句话说,可以只需编写以下内容: Sub RelaxedOnClick Handles btn.Click MessageBox.Show("Hello World from" & btn.Text) End Sub
可以这样理解,宽松委托在使用 AddressOf 或委托创建表达式构造委托时也适用,即使方法组为后期绑定调用: Dim F As EventHandler = AddressOf RelaxedOnClick Dim G As New EventHandler(AddressOf btn.Click)
结论
Visual Basic 9.0 统一了对数据的访问,不管数据源自关系数据库、XML 文档还是任意对象图,也不管以什么方式保持或者存储在内存中。该统一包括样式、方法、工具和编程模式。Visual Basic 的语法极其灵活,可以轻松地将类似 XML 文字的扩展和类似 SQL 的查询表达式添加到该语言深处。这大大减少了新的 .NET 语言集成查询 API 的“外围”,通过 IntelliSense 和智能标记提高了数据访问功能的可发现性,并通过将外部语法从字符串数据中提取到 Visual Basic 中从而大大改进了调试和编译时检查功能。
此外,诸如类型推断、对象初始值设定项和宽松委托之类的功能大大减少了代码冗余以及程序员需要学习和记忆或查找的规则异常数目,而且不会对性能产生任何影响。
虽然可能看起来 Visual Basic 9.0 中的新增功能很多,但是我们希望上述主题将使您确信,它是一致的、及时的并致力于使 Visual Basic 成为世界上最好的编程语言。我们希望它也能激发您的创造力,并希望您也相信使用 Visual Basic 将有更加美妙的体验。
(编辑:aniston)
|