查询操作中的类型关系 (Visual Basic)

语言集成查询 (LINQ) 查询操作中使用的变量是强类型化的,并且必须相互兼容。 强类型化在数据源、查询本身和查询执行中使用。 下图标识了用于描述 LINQ 查询的术语。 有关查询部分的详细信息,请参阅基本查询操作 (Visual Basic)

Screenshot showing a pseudocode query with elements highlighted.

查询中范围变量的类型必须与数据源中元素的类型兼容。 查询变量的类型必须与 Select 子句中定义的序列元素兼容。 最后,序列元素的类型还必须与执行查询的 For Each 语句中使用的循环控制变量的类型兼容。 这种强类型化有助于在编译时识别类型错误。

Visual Basic 通过实现局部类型推理(也称为隐式类型化)使强类型化变得方便。 以上示例中使用了该功能,你将发现,每个 LINQ 示例和整篇文档中也使用了该功能。 在 Visual Basic 中,只需使用不带 As 子句的 Dim 语句即可实现局部类型推理。 在以下示例中,city 强类型化为字符串。

Dim city = "Seattle"

备注

仅当 Option Infer 设置为 On 时,局部类型推理才能正常进行。 有关详细信息,请参阅 Option Infer 语句

但是,即使在查询中使用局部类型推理,数据源中的变量、查询变量与查询执行循环之间也存在相同的类型关系。 如果对这些类型关系有基本的了解,在编写 LINQ 查询或学习文档中的示例和代码示例时将很有帮助。

对于与从数据源返回的类型不匹配的范围变量,可能需要指定显式类型。 可以使用 As 子句指定范围变量的类型。 但是,如果转换是收缩转换并且 Option Strict 设置为 On,则这样做会导致错误。 因此,我们建议对从数据源检索的值执行转换。 可以使用 Cast 方法将数据源中的值转换为显式范围变量类型。 还可以将 Select 子句中选择的值强制转换为与范围变量类型不同的显式类型。 以下代码中演示了这些要点。

Dim numbers1() As Integer = {1, 2, 4, 16, 32, 64}
Dim numbers2() As Double = {5.0#, 10.0#, 15.0#}

' This code does not result in an error.
Dim numberQuery1 = From n As Integer In numbers1 Where n > 5

' This code results in an error with Option Strict set to On. The type Double
' cannot be implicitly cast as type Integer.
Dim numberQuery2 = From n As Integer In numbers2 Where n > 5

' This code casts the values in the data source to type Integer. The type of
' the range variable is Integer.
Dim numberQuery3 = From n In numbers2.Cast(Of Integer)() Where n > 5

' This code returns the value of the range variable converted to Integer. The type of
' the range variable is Double.
Dim numberQuery4 = From n In numbers2 Where n > 5 Select CInt(n)

返回源数据整个元素的查询

以下示例演示了一个 LINQ 查询操作,该操作返回从源数据选择的元素序列。 源 names 包含字符串数组,查询输出是包含以字母 M 开头的字符串的序列。

Dim names = {"John", "Rick", "Maggie", "Mary"}
Dim mNames = From name In names
             Where name.IndexOf("M") = 0
             Select name

For Each nm In mNames
    Console.WriteLine(nm)
Next

此代码等效于以下代码,但更短且更易于编写。 在查询中依赖于局部类型推理是 Visual Basic 中的首选样式。

Dim names2 = {"John", "Rick", "Maggie", "Mary"}
Dim mNames2 As IEnumerable(Of String) =
    From name As String In names
    Where name.IndexOf("M") = 0
    Select name

For Each nm As String In mNames
    Console.WriteLine(nm)
Next

无论类型是隐式还是显式确定的,上面两个代码示例中都存在以下关系。

  1. 数据源中元素的类型 names 是查询中范围变量的类型 name

  2. 所选对象的类型 name 确定了查询变量的类型 mNames。 此处 name 是一个字符串,因此 Visual Basic 中的查询变量是 IEnumerable(Of String)。

  3. mNames 中定义的查询在 For Each 循环中执行。 该循环迭代查询执行结果。 由于 mNames 在执行时会返回字符串序列,因此循环将迭代变量 nm(也是一个字符串)。

从所选元素返回一个字段的查询

以下示例演示了一个 LINQ to SQL 查询操作,该操作返回一个序列,其中仅包含从数据源选择的每个元素的一部分。 该查询将 Customer 对象的集合用作数据源,并仅在结果中投影 Name 属性。 由于客户名称是一个字符串,因此查询将生成字符串序列作为输出。

' Method GetTable returns a table of Customer objects.
Dim customers = db.GetTable(Of Customer)()
Dim custNames = From cust In customers
                Where cust.City = "London"
                Select cust.Name

For Each custName In custNames
    Console.WriteLine(custName)
Next

变量之间的关系类似于较简单示例中的关系。

  1. 数据源中元素的类型 customers 是查询中范围变量的类型 cust。 在此示例中,该类型为 Customer

  2. Select 语句返回每个 Customer 对象的 Name 属性,而不是整个对象。 由于 Name 是一个字符串,因此查询变量 custNames 同样是 IEnumerable(Of String),而不是 Customer

  3. 由于 custNames 表示字符串序列,因此 For Each 循环的迭代变量 custName 必须是字符串。

如果不使用局部类型推理,以上示例将更难编写和理解,如以下示例所示。

' Method GetTable returns a table of Customer objects.
 Dim customers As Table(Of Customer) = db.GetTable(Of Customer)()
 Dim custNames As IEnumerable(Of String) =
     From cust As Customer In customers
     Where cust.City = "London"
     Select cust.Name

 For Each custName As String In custNames
     Console.WriteLine(custName)
 Next

需要匿名类型的查询

以下示例演示了更复杂的情况。 在以上示例中,不方便为所有变量显式指定类型。 在此示例中,根本做不到这一点。 此查询中的 Select 子句不是从数据源中选择整个 Customer 元素,也不是从每个元素中选择单个字段,而是返回原始 Customer 对象的两个属性:NameCity。 在对 Select 子句的响应中,编译器定义了包含这两个属性的匿名类型。 在 For Each 循环中执行 nameCityQuery 后的结果是新匿名类型的实例集合。 由于匿名类型没有可用的名称,因此无法显式指定 nameCityQuerycustInfo 的类型。 也就是说,使用匿名类型时,无法使用类型名称来代替 IEnumerable(Of String) 中的 String。 有关详细信息,请参阅匿名类型

' Method GetTable returns a table of Customer objects.
Dim customers = db.GetTable(Of Customer)()
Dim nameCityQuery = From cust In customers
                    Where cust.City = "London"
                    Select cust.Name, cust.City

For Each custInfo In nameCityQuery
    Console.WriteLine(custInfo.Name)
Next

尽管无法为以上示例中的所有变量指定类型,但关系保持不变。

  1. 数据源中元素的类型同样是查询中范围变量的类型。 在此示例中,custCustomer 的实例。

  2. 由于 Select 语句生成匿名类型,因此查询变量 nameCityQuery 必须隐式类型化为匿名类型。 匿名类型没有可用的名称,因此无法显式指定。

  3. For Each 循环中的迭代变量的类型是在步骤 2 中创建的匿名类型。 由于该类型没有可用的名称,因此必须隐式确定循环迭代变量的类型。

请参阅