本书翻译目的为个人学习和知识共享,其版权属原作者所有,如有侵权,请告知本人,本人将立即对发帖采取处理。
允许转载,但转载时请注明本版权声明信息,禁止用于商业用途!
博客园:韩现龙
Introducing to Microsoft LINQ目录
Visual Basic9.0的大部分新特性在C#3.0中都有与之相对应的部分。为简明起见,这部分主要讲述Visual Basic 9.0所特有的语法。这种关于内在的和对新特性的可能的使用和第二章中我们对C#3.0的特性是相同的。
本地类型推断(Local Type Inference)
本地类型推断特性又叫做隐示类型的本地变量(implicitly typed local variables)。它允许我们通过从指定的表达式中推断类型来定义变量。可能Visual Basic的开发人员第一次看到它时会以为这种特性和使用了Option Strict Off实现的效果是一样的。事实上,本地类型推断得到的是一个强类型的变量。Listing 3-3 的代码事例展示了这个语法,注释表明了声明的变量的实际类型:
Listing 3-3: Local type inference
Dim x = 2.3 ' Double2
Dim y = x ' Double3
Dim r = x / y ' Double4
Dim s = "sample" ' String5
Dim l = s.Length ' Integer6
Dim w = d ' Decimal7
Dim o ' Object – allowed only with Option Strict Off
这和我们在C#中见到的是一样的。Visual Basic的语法仅是简单的活力了As部分。如果开启了Option Strict On的话,o的声明是非法的。注意即便开启了Option Strict Off,所有的变量类型也是能够从初始化表达式中推断出来的。事实上,上面的代码用Visual Basic8.0也是可以编译的,但是在VB8.0中所有的变量都是Oject类型,将初始化表达式中的将所有的值进行装箱操作。
小贴士 如果没有较好的理由避免装箱操作,我们推荐使用Option Strict On。比如说,在没有一个基础的互操作程序集而通过互操作访问一个COM对象时,Option Strict Off的延迟绑定(late-binding)行为对于调用没有暴露在COM对象的类型词典中的实现了Idispatch接口的方法有用。
使用Option Strct On可以帮助我们避免一些可能发生的错误。例如,请思考Listing3-4的代码:
Listing 3-4: Changed behavior from Visual Basic 8.0 to Visual Basic 9.0
1Option Strict Off
2Module LocalTypeInference
3Sub BeCareful()
4Dim a = 10
5a = "Hello"
6Console.WriteLine(a)
7End Sub
8'
9End Module
10
在Visual Basic8.0中,变量a是一个Object类型,因为我们总是可以将不同类型值的值赋给它,因为它最终是被装箱的。在Visual Basic 8.0中,执行Becareful方法后输出的是一个字符串Hello。而在Visual Basic 9.0中,如果使用了Option Strict Off,在将一个String(没有数字的)类型赋值给一个Integer变量时,将会发生如下异常:
Unhandled Exception: System.InvalidCastException: Conversion from string "Hello" to type 'Integer' is not valid. ---> System.FormatException: Input string was not in a correct format
如果使用了Option Strict On的话,在Listing3-4中的代码编译期就不会通过。Visual Basic 8.0不接受该声明;Visual Basic9.0也不会将Hello字符串赋值给一个integer类型。如果在编程时你经常使用Option Strict Off的话,那么在将已有的Visual Basic代码和LINQ进行迁移时请注意这一点。
重要 通过Option Infer Off我们可以禁用本地类型推断(local type inference)。默认情况下VisualBasic9.0的新项目使用的是Option Infer On.在从以前版本的VB迁移代码时,为了避免一些可能出现的问题,请使用Option Infer Off.
扩展方法(Extension Methods)
在Visual Basic9.0中,可以使用生成的和C#结果集一样的技术去定义扩展方法。此处我们仅将专注于在C#和VB间语法的不同之处。在Listing 3-5中的代码使用传统的方法声明,并且在特定的Culture下调用了一个将decimal值转换为string类型的方法。
Listing 3-5: Standard method declaration and use
Module Demo2
Sub DemoTraditional()3
Dim x As Decimal = 1234.5684
Console.WriteLine(FormattedIT(x))5
Console.WriteLine(FormattedUS(x))6
End Sub7
End Module8

9
Public Class TraditionalMethods10
Shared Function FormattedIT(ByVal d As Decimal) As String11
Return String.Format(formatIT, "{0:#,0.00}", d)12
End Function13

14
Shared Function FormattedUS(ByVal d As Decimal) As String15
Return String.Format(formatUS, "{0:#,0.00}", d)16
End Function17

18
Shared formatUS As CultureInfo = New CultureInfo("en-US")19
Shared formatIT As CultureInfo = New CultureInfo("it-IT")20
End Class同C#一样,我们可以将上面这个代码转换为扩展decimal类型的方法。不像在C#中那样添加关键字(在C#中向首参添加this关键字),在VB中我们可以用System.Runtime.CompilerServices.Extension属性去修饰扩展方法和包括的类。
小贴士 在方法声明时,当遇到“this”关键字时,C#编译器会自动生成Extention修饰属性的。而在Visual Basic中,这个工作交由了程序员来做。
为了使用短一些的属性名称,我们向添加了Imports System.Runtime.CompilerServices 声明,如Listing3-6所示:
Listing 3-6: Extension method declaration
Imports System.Runtime.CompilerServices2
<Extension()> _3
Public Module ExtensionMethods4
<Extension()> _5
Public Function FormattedIT(ByVal d As Decimal) As String6
Return String.Format(formatIT, "{0:#,0.00}", d)7
End Function8

9
<Extension()> _10
Public Function FormattedUS(ByVal d As Decimal) As String11
Return String.Format(formatUS, "{0:#,0.00}", d)12
End Function13

14
Private formatUS As CultureInfo = New CultureInfo("en-US")15
Private formatIT As CultureInfo = New CultureInfo("it-IT")16
End Module扩展方法必须定义在Module中定义,并且方法体和方法所在模块必须用Extension属性进行修饰。首个参数类型就是该方法扩展的类型。通常情况下,扩展方法和扩展方法所在的模块中是被声明为public的,因为我们通常在对其进行声明了的程序集外对其进行调用。
小贴士 在这点上,编译之后的的扩展方法模块包括了元数据,就如我们用MustInherit 和NotInheritable 关键字进行定义的类一样,它是不被编译器允许的语法。在使用ILDASM (Intermediate Language Disassembler) 或者Reflector对其进行反编译时,我们必须理解这点和C#中的静态类相对应的情况。ILDASM是.NET Framework SDK的一个工作。Reflector是支持对若干种语言(包括C#和VisualBasic)进行反编译的工具,请下载请到此处:http://www.aisto.com/roeder/dotnet
在VB代码中使用扩展方法要求扩展方法所在的类用作Imports声明的参数之一。这和C#是不同的,在C#中要求using声明所在空间即可而并非某个精确的类。在Listing3-7中,我们看一下调用在前面例子中声明的扩展方法。我们用了Imports ExtensionVB.ExtensionMethods 语句声明,因为所在的命名空间是ExtensionVB,并且ExtensionMethods 是我们的扩展方法所在的类的名。
Listing 3-7: Extension method use
Imports ExtensionVB.ExtensionMethods4

5
Module Demo6
Sub DemoExtension()7
Dim x As Decimal = 1234.5688
Console.WriteLine(x.FormattedIT())9
Console.WriteLine(x.FormattedUS())10
End Sub11
End Module除了我们在这里高亮显示的语法上的不同之外,第2章中的关于扩展方法的讲解在这里都是有效的。
对象初始化表达式(Object Initialization Expressions)
Visual Basic 9.0对同一类型的实例初始化多个成员提供了一种新的语法。With做的是同样的工作,但是VB9.0中的对象初始化器允许在单一的表达式中对多个成员进行初始化。稍后我们将看到,这种功能对于初始化匿名类型是必需的。考虑一下在Listing3-8中的类:
Listing 3-8: Sample class for object initializers
Public Class Customer4
Public Age As Integer5
Public Name As String6
Public Country As String7
Public Sub New(ByVal name As String, ByVal age As Integer)8
Me.Age = age9
Me.Name = name10
End Sub11
' 
12
End Class如果在Customer类实例中我们想初始化Country而不想初始化Age,在VB8.0中,基于With关键字我们可以用如下代码实现:
Listing 3-9: Initialization using With statement
Dim customer As New Customer4
With customer5
.Name = "Marco"6
.Country = "Italy"7
End With
在Visual Basic 9.0中的新对象初始化器语法允许以不同的方式对该对象进行初始化,如Listing3-10所示:
Listing 3-10: Object initializer syntax
Dim customer As Customer2
customer = New Customer With {.Name = "Marco", .Country = "Italy"}对象初始化器语法需要在对象创建之后紧跟With关键字,紧随其后的是在大括号中的要初始化的成员列表。每个成员名称以“点”做前缀,然后是“=”,接着就是初始化表达式。多个成员之间以逗号(,)隔开。C#程序员应该明白在VisualBasic中若想将同一个表达式分开多行显示的话,需要以一个特殊的字符(即下划线_)在每一行的末尾进行连接。在Listing3-11中所示:
Listing 3-11: Object initializer syntax on multiple lines
2
Dim customer As Customer = _3
New Customer With { _4
.Name = "Marco", _5
.Country = "Italy"}
使用续行符会使对象初始化器的失效。我们之所以喜欢使用新的对象初始化器语法而不用传统的With声明,是因为我们可以将所有的写在单独的一行上。例如,我们可以使用本地类型推断(local type inference)写出如Listing3-12的代码:
Listing 3-12: Object initializer syntax and local type inference
Dim customer = New Customer With {.Name = "Marco", .Country = "Italy"}
在赋值时,对象初始化器可以使用常量或者其他的表达式。此外,我们可以使用在对非默认的构造器。Listing 3-13中的代码说明了这些概念:
Listing 3-13: Object initializers using expressions
Dim c1 = New Customer With {.Name = "Marco", .Country = "Italy"}2
Dim c2 = New Customer("Paolo", 21) With {.Country = "Italy"}3
Dim c3 = New Customer With {.Name = "Paolo", .Age = 21, .Country = "Italy"}4
Dim c4 = New Customer With {.Name = c1.Name, .Country = c2.Country, .Age = c2.Age}
因为对象初始化器是一个表达式,所以它可以被嵌套使用。Listing3-14展示了可能的用法:
Listing 3-14: Nested object initializers
' In Point and Rectangle classes, we collapsed parts of implementation2
Public Class Point3
Private _x, _y As Integer4
Public Property X
' Integer - implementation collapsed5
Public Property Y
' Integer - implementation collapsed6
End Class7

8
Public Class Rectangle9
Private _tl, _br As Point10
Public Property TL()
' Point - implementation collapsed11
Public Property BR()
' Point - implementation collapsed12
End Class13

14
' Possible code inside a method15
Dim r = New Rectangle With { _16
.TL = New Point With {.X = 0, .Y = 1}, _17
.BR = New Point With {.X = 2, .Y = 3} _18
}匿名类型就是不用标识符声明的类型。在Visual Basic 9.0中的匿名类型对C#3.0中相应的特性是相一致的。我们可以在不指名类名的情况下用New操作符去创建一个对象。此时,一个新类(匿名类型)就创建了。思考Listing3-15的代码:
Listing 3-15: Anonymous type definition
Dim c1 As New Customer With {.Name = "Marco"}2
Dim c2 = New Customer With {.Name = "Paolo"}3
Dim c3 = New With {.Name = "Tom", .Age = 31}4
Dim c4 = New With {c2.Name, c2.Age}5
Dim c5 = New With {c1.Name, c1.Country}6
Dim c6 = New With {c1.Country, c1.Name}
变量c1和c2的类型是Customer类型,但是变量c3,c4,c5,和c6仅仅靠代码是无法简单地推断出它的类型的。本地类型推断会从指定的表达式中推断出变量的类型,但是在New关键字之后我们并没有显示地声明类型。这种对象初始化器生成了一个新类。
生成的类有个公共的属性和一个为每个参数而存在的私有字段,属性名称和类型从对象初始化器中推断出来。对于匿名类中的各个属性来说,如果它们的名字、类型相同,并且顺序也一样,那么生成的匿名类就是一样的。通过下面的代码我们可以看到生成的类型名称:
1Console.WriteLine("c1 is {0}", c1.GetType())
2Console.WriteLine("c2 is {0}", c2.GetType())
3Console.WriteLine("c3 is {0}", c3.GetType())
4Console.WriteLine("c4 is {0}", c4.GetType())
5Console.WriteLine("c5 is {0}", c5.GetType())
6Console.WriteLine("c6 is {0}", c6.GetType())
输出如下:
c1 is AnonymousTypes.Customer c2 is AnonymousTypes.Customer c3 is VB$AnonymousType_0`2[System.String,System.Int32] c4 is VB$AnonymousType_0`2[System.String,System.Int32] c5 is VB$AnonymousType_1`2[System.String,System.String] c6 is VB$AnonymousType_2`2[System.String,System.String]
通过代码是不能推断出匿名类型的名称的(因为我们不知道生成的名字是什么),但是我们在类实例中对其进行查询。因为变量c3和c4有相同的字段和属性,所以它们是相同的匿名类型。虽然c5和c6有相同的属性(类型和名称),但是它们的顺序却不同,这就足以令编译器认为二者是两个不同的匿名类型。
如果对集合初始化器使用该语法,我们就可以创建一个匿名类型的数组,如Listing 3-16所示:
Listing 3-16: Array of anonymous types
Dim ca = {New With {.Name = "Marco", .Country = "Italy"}, _2
New With {.Name = "Tom", .Country = "USA"}, _3
New With {.Name = "Paolo", .Country = "Italy"} _4
}5

6
Dim cs = {New With {.Name = "Marco", .Sports = {"Tennis", "Spinning"}}, _7
New With {.Name = "Tom", .Sports = {"Rugby", "Squash", "Baseball"}}, _8
New With {.Name = "Paolo", .Sports = {"Skateboard", "Windsurf"}} _9
}
第一个数组ca由有两个String类型成员的匿名类型的实例组成。第二个数组cs是由不同匿名类型组成的数组,该匿名类型由Name字符串和Sports字符串数组组成。
查询表达式(Query Expressions)
Visual Basic9.0像C#3.0那样支持查询表达式(和SQL语言类似的用来操作数据的表达式)。Visual Basic9.0的初期文档为了声明这种语言集成(language-integrated)语法而讨论了对查询的理解。在第四章中将会对查询表达式的关键字进行一个详细的解释。这部分讲述了查询表达式的语法在C#和Visual Basic9.0之间的不同之处。读完第四章之后一些具体细节可能会更清楚。
如Listing 3-17所示,用Visual Basic语言以From...Where...Select..模式来写LINQ查询表达式。
Listing 3-17: Simple LINQ query
Dim customers() = { _2
New With {.Name = "Marco", .Discount = 4.5}, _3
New With {.Name = "Paolo", .Discount = 3.0}, _4
New With {.Name = "Tom", .Discount = 3.5} _5
}6

7
Dim query = _8
From c In customers _9
Where c.Discount > 3 _10
Select New With {c.Name, .Perc = c.Discount / 100}11

Where语句是可选的,但是From和Select是必须的。如果出现了Where语句,编译器会将作为Where条件的谓语转换为lambda表达式(下一节中将会讲到)。另外一个嵌套函数的功能就是投影(Select关键字之后的代码)。
在这种有Order By语句的情况下,关键字的顺序和C#3.0中是不同的。例如,考虑Listing3-18中的C#3.0代码:
Listing 3-18: Order By in C# 3.0
var query =2
from c in customers3
where c.Discount > 34
orderby perc5
select new {c.Name, Perc = c.Discount / 100)
Listing 3-18中的代码用Visual Basic9.0来写即如Listing 3-19所示:
Listing 3-19: Order By in Visual Basic 9.0
Dim query = _2
From c In customers _3
Where c.Discount > 3 _4
Select r = New With {c.Name, .Perc = c.Discount / 100} _5
Order By r.Perc
注意在C#3.0中Order By在select之前,而在Visual Basic9.0却是出现在Select之后。
Lambda表达式(Lambda Expressions)
C#3.0允许我们用如下的语法来写lambda表达式:
1(c) => c.Country == "USA"
2( a, b ) => a + b
对应于Visual Basic9.0的语法是基于Function关键字的,如Listing3-20所示:
Listing 3-20: Lambda expressions in Visual Basic 9.0
Function(c) c.Country = "USA"
Function( a, b ) a + b用lambda表达式我们可以写出更为复杂的查询表达式。如Listing3-21所示:
Listing 3-21: Use of lambda expressions in query expressions
Dim customers As List(Of Customer) = New List(Of Customer)2
Dim query = customers.FindAll( Function(c) c.Country = "USA" );当查询表达式存在代理作为参数时,在C#3.0中我们使用lambda表达式以更为简短和方便的方式来定义代理。在Visual Basic 9.0中我们也同样可以使用lambda表达式,编译器通过创建closures也生成同样的结果,closures是将它的context保存到一个暂时存储区中,并且将它传递到一个隐藏的方法调用中。例如Lising 3-22中的代码,编译器生成两个代表了将要传入到Select和Where函数中的代理。
Listing 3-22: Closures with query expressions
Dim maxPlayers = 22
Dim players = _3
From customer In customers _4
Where customer.Sports.Count > maxPlayers _5
Select customer.Name6

两个lambda表达式生成了两个相应的closures.这些closures和声明了players变量声明的方法有相同的作用域。这种设置对于访问maxPlayers变量必需的。生成的代码和下面的代码是等价的:
Dim maxPlayers = 22
Dim players = _3
Enumerable.Select(4
Enumerable.Where( customers,5
Function(customer) customer.Sports.Count > maxPlayers ),6
Function(customer) customer.Name );注:介绍VB部分代码并未经译者测试,请阅读者自行进行测试,本部分译文仅供参考。

浙公网安备 33010602011771号