本书翻译目的为个人学习和知识共享,其版权属原作者所有,如有侵权,请告知本人,本人将立即对发帖采取处理。
允许转载,但转载时请注明本版权声明信息,禁止用于商业用途!

博客园:韩现龙


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

1Dim x = 2.3      ' Double
2Dim y = x        ' Double
3Dim r = x / y    ' Double
4Dim s = "sample" ' String
5Dim l = s.Length ' Integer
6Dim w = d        ' Decimal
7Dim 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
3    Sub BeCareful()
4        Dim a = 10
5        a = "Hello"
6        Console.WriteLine(a)
7    End Sub

8    '
9End Module

10

Image from book 在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类型的方法。

Image from bookListing 3-5: Standard method declaration and use

1Module Demo
2    Sub DemoTraditional()
3        Dim x As Decimal = 1234.568
4        Console.WriteLine(FormattedIT(x))
5        Console.WriteLine(FormattedUS(x))
6    End Sub

7End Module

8
9Public Class TraditionalMethods
10    Shared Function FormattedIT(ByVal d As Decimal) As String
11        Return String.Format(formatIT, "{0:#,0.00}", d)
12    End Function

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

17
18    Shared formatUS As CultureInfo = New CultureInfo("en-US")
19    Shared formatIT As CultureInfo = New CultureInfo("it-IT")
20End 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

1Imports System.Runtime.CompilerServices
2<Extension()> _
3Public Module ExtensionMethods
4    <Extension()> _
5    Public Function FormattedIT(ByVal d As Decimal) As String
6        Return String.Format(formatIT, "{0:#,0.00}", d)
7    End Function

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

13
14    Private formatUS As CultureInfo = New CultureInfo("en-US")
15    Private formatIT As CultureInfo = New CultureInfo("it-IT")
16End Module

扩展方法必须定义在Module中定义,并且方法体和方法所在模块必须用Extension属性进行修饰。首个参数类型就是该方法扩展的类型。通常情况下,扩展方法和扩展方法所在的模块中是被声明为public的,因为我们通常在对其进行声明了的程序集外对其进行调用。

小贴士  在这点上,编译之后的的扩展方法模块包括了元数据,就如我们用MustInheritNotInheritable 关键字进行定义的类一样,它是不被编译器允许的语法。在使用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

3Imports ExtensionVB.ExtensionMethods
4
5Module Demo
6    Sub DemoExtension()
7        Dim x As Decimal = 1234.568
8        Console.WriteLine(x.FormattedIT())
9        Console.WriteLine(x.FormattedUS())
10    End Sub

11End Module

除了我们在这里高亮显示的语法上的不同之外,第2章中的关于扩展方法的讲解在这里都是有效的。

对象初始化表达式(Object Initialization Expressions)

Visual Basic 9.0对同一类型的实例初始化多个成员提供了一种新的语法。With做的是同样的工作,但是VB9.0中的对象初始化器允许在单一的表达式中对多个成员进行初始化。稍后我们将看到,这种功能对于初始化匿名类型是必需的。考虑一下在Listing3-8中的类:

Listing 3-8: Sample class for object initializers

3Public Class Customer
4    Public Age As Integer
5    Public Name As String
6    Public Country As String
7    Public Sub New(ByVal name As String, ByVal age As Integer)
8        Me.Age = age
9        Me.Name = name
10    End Sub

11    ' 
12End Class

如果在Customer类实例中我们想初始化Country而不想初始化Age,在VB8.0中,基于With关键字我们可以用如下代码实现:

Listing 3-9: Initialization using With statement Image from book

3Dim customer As New Customer
4With customer
5    .Name = "Marco"
6    .Country = "Italy"
7End With

 

在Visual Basic 9.0中的新对象初始化器语法允许以不同的方式对该对象进行初始化,如Listing3-10所示:

Listing 3-10: Object initializer syntax Image from book

1Dim customer As Customer
2customer = New Customer With {.Name = "Marco", .Country = "Italy"}

对象初始化器语法需要在对象创建之后紧跟With关键字,紧随其后的是在大括号中的要初始化的成员列表。每个成员名称以“点”做前缀,然后是“=”,接着就是初始化表达式。多个成员之间以逗号(,)隔开。C#程序员应该明白在VisualBasic中若想将同一个表达式分开多行显示的话,需要以一个特殊的字符(即下划线_)在每一行的末尾进行连接。在Listing3-11中所示:

Listing 3-11: Object initializer syntax on multiple lines Image from book


2Dim 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 Image from book

3Dim customer = New Customer With {.Name = "Marco", .Country = "Italy"}

在赋值时,对象初始化器可以使用常量或者其他的表达式。此外,我们可以使用在对非默认的构造器。Listing 3-13中的代码说明了这些概念:

Listing 3-13: Object initializers using expressions Image from book

1Dim c1 = New Customer With {.Name = "Marco", .Country = "Italy"}
2Dim c2 = New Customer("Paolo", 21) With {.Country = "Italy"
}
3Dim c3 = New Customer With {.Name = "Paolo", .Age = 21, .Country = "Italy"
}
4Dim c4 = New Customer With {.Name = c1.Name, .Country = c2.Country, .Age =
c2.Age}

 

因为对象初始化器是一个表达式,所以它可以被嵌套使用。Listing3-14展示了可能的用法:

Listing 3-14: Nested object initializers Image from book

1' In Point and Rectangle classes, we collapsed parts of implementation
2Public Class Point
3    Private _x, _y As Integer
4    Public Property X  ' Integer - implementation collapsed
5    Public Property Y  ' Integer - implementation collapsed
6End Class

7
8Public Class Rectangle
9    Private _tl, _br As Point
10    Public Property TL()  ' Point - implementation collapsed
11    Public Property BR()  ' Point - implementation collapsed
12End Class

13
14' Possible code inside a method
15    Dim r = New Rectangle With { _
16                .TL = New Point With {.X = 0, .Y = 1}, _
17                .BR = New Point With {.X = 2, .Y = 3} _
18            }

匿名类型(Anonymous Types)

匿名类型就是不用标识符声明的类型。在Visual Basic 9.0中的匿名类型对C#3.0中相应的特性是相一致的。我们可以在不指名类名的情况下用New操作符去创建一个对象。此时,一个新类(匿名类型)就创建了。思考Listing3-15的代码:

Listing 3-15: Anonymous type definition Image from book

1Dim c1 As New Customer With {.Name = "Marco"}
2Dim c2 = New Customer With {.Name = "Paolo"}
3Dim c3 = New With {.Name = "Tom", .Age = 31}
4Dim c4 = New With {c2.Name, c2.Age}
5Dim c5 = New With {c1.Name, c1.Country}
6Dim c6 = New With {c1.Country, c1.Name}

变量c1c2的类型是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有相同的字段和属性,所以它们是相同的匿名类型。虽然c5c6有相同的属性(类型和名称),但是它们的顺序却不同,这就足以令编译器认为二者是两个不同的匿名类型。

 

如果对集合初始化器使用该语法,我们就可以创建一个匿名类型的数组,如Listing 3-16所示:

Listing 3-16: Array of anonymous types Image from book

1Dim ca = {New With {.Name = "Marco", .Country = "Italy"}, _
2          New With {.Name = "Tom", .Country = "USA"}, _
3          New With {.Name = "Paolo", .Country = "Italy"} _
4         }
5
6Dim 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 

1Dim 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
7Dim query = _
8    From c In customers _
9    Where c.Discount > 3 _
10    Select New With {c.Name, .Perc = c.Discount / 100}
11

Where语句是可选的,但是FromSelect是必须的。如果出现了Where语句,编译器会将作为Where条件的谓语转换为lambda表达式(下一节中将会讲到)。另外一个嵌套函数的功能就是投影(Select关键字之后的代码)。

在这种有Order By语句的情况下,关键字的顺序和C#3.0中是不同的。例如,考虑Listing3-18中的C#3.0代码:

Listing 3-18: Order By in C# 3.0

1var query =
2from    c in customers
3where   c.Discount > 3
4    orderby perc
5select  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 Image from book

1Dim query = _
2From c In customers _
3Where c.Discount > 3 _
4Select r = New With {c.Name, .Perc = c.Discount / 100} _
5    Order By r.Perc

 

注意在C#3.0中Order Byselect之前,而在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 Image from book 

1Dim customers As List(Of Customer) = New List(Of Customer)
2Dim query = customers.FindAll( Function(c) c.Country = "USA" );

Closures

当查询表达式存在代理作为参数时,在C#3.0中我们使用lambda表达式以更为简短和方便的方式来定义代理。在Visual Basic 9.0中我们也同样可以使用lambda表达式,编译器通过创建closures也生成同样的结果,closures是将它的context保存到一个暂时存储区中,并且将它传递到一个隐藏的方法调用中。例如Lising 3-22中的代码,编译器生成两个代表了将要传入到SelectWhere函数中的代理。

Listing 3-22: Closures with query expressions Image from book 

1Dim maxPlayers = 2
2Dim players = _
3From customer In customers _
4Where customer.Sports.Count > maxPlayers _
5Select customer.Name
6

两个lambda表达式生成了两个相应的closures.这些closures和声明了players变量声明的方法有相同的作用域。这种设置对于访问maxPlayers变量必需的。生成的代码和下面的代码是等价的:

1Dim maxPlayers = 2
2Dim players = _
3    Enumerable.Select(
4        Enumerable.Where( customers,
5            Function(customer) customer.Sports.Count > maxPlayers ),
6            Function(customer) customer.Name );

注:介绍VB部分代码并未经译者测试,请阅读者自行进行测试,本部分译文仅供参考。
posted on 2008-03-18 14:41  是谁啊?  阅读(2174)  评论(7编辑  收藏  举报