匿名类型 (Visual Basic)

Visual Basic 支持匿名类型,匿名类型使你无需为数据类型编写类定义即可创建对象。 此时,编译器将为你生成类。 该类没有可使用的名称,直接继承自 Object,且包含在声明对象时指定的属性。 由于未指定数据类型的名称,因此将其称为匿名类型。

下面的示例声明变量 product,并将其创建为具有两个属性(NamePrice)的匿名类型的实例。

' Variable product is an instance of a simple anonymous type.
Dim product = New With {Key .Name = "paperclips", .Price = 1.29}

查询表达式使用匿名类型来合并查询选择的数据列。 由于无法预测特定查询可能选择的列,因此无法提前定义结果的类型。 匿名类型让你可以编写一个查询来按任意顺序选择任意数量的列。 编译器创建与指定属性和指定顺序匹配的数据类型。

在下面的示例中,products 是产品对象的列表,每个对象都有许多属性。 变量 namePriceQuery 保存查询的定义,执行该查询后将返回具有两个属性(NamePrice)的匿名类型的实例集合。

Dim namePriceQuery = From prod In products
                     Select prod.Name, prod.Price

变量 nameQuantityQuery 保存查询的定义,执行该查询后将返回具有两个属性(NameOnHand)的匿名类型的实例集合。

Dim nameQuantityQuery = From prod In products
                        Select prod.Name, prod.OnHand

若要详细了解编译器为匿名类型创建的代码,请参阅匿名类型定义

注意

匿名类型的名称由编译器生成,因具体的编译而异。 代码不应使用或依赖匿名类型的名称,因为重新编译项目时名称可能会发生变化。

声明匿名类型

声明匿名类型实例时,将使用初始值设定项列表来指定类型的属性。 在声明匿名类型时只能指定属性,不能指定其他类元素(例如方法或事件)。 在下面的示例中,product1 是具有 NamePrice 这两个属性的匿名类型的实例。

' Variable product1 is an instance of a simple anonymous type.
Dim product1 = New With {.Name = "paperclips", .Price = 1.29}
' -or-
' product2 is an instance of an anonymous type with key properties.
Dim product2 = New With {Key .Name = "paperclips", Key .Price = 1.29}

如果将属性指定为键属性,则可以用其比较两个匿名类型实例是否相等。 但无法更改键属性的值。 有关详细信息,请参阅本主题后面的“键属性”部分。

注意,声明匿名类型实例就和使用对象初始值设定项声明命名类型的实例一样:

' Variable product3 is an instance of a class named Product.
Dim product3 = New Product With {.Name = "paperclips", .Price = 1.29}

若要详细了解用于指定匿名类型属性的其他方法,请参阅如何:推断匿名类型声明中的属性名和类型

键属性

键属性与非键属性有几大根本上的差异:

  • 为确定两个实例是否相等,只比较键属性的值。

  • 键属性的值是只读的,不能更改。

  • 对于匿名类型,编译器生成的哈希代码算法中仅包含键属性值。

等式

匿名类型的实例只有在它们属于相同匿名类型的实例时才相等。 如果两个实例满足以下条件,编译器就会将其视为相同类型的实例:

  • 它们在同一程序集中声明。

  • 其属性具有相同的名称、相同的推断类型,并按相同的顺序声明。 名称比较不区分大小写。

  • 每个实例中相同的属性均被标记为键属性。

  • 每个声明中至少有一个属性是键属性。

没有键属性的匿名类型的实例仅与自身相等。

' prod1 and prod2 have no key values.
Dim prod1 = New With {.Name = "paperclips", .Price = 1.29}
Dim prod2 = New With {.Name = "paperclips", .Price = 1.29}

' The following line displays False, because prod1 and prod2 have no
' key properties.
Console.WriteLine(prod1.Equals(prod2))

' The following statement displays True because prod1 is equal to itself.
Console.WriteLine(prod1.Equals(prod1))

属于相同匿名类型的两个实例相等(如果其键属性的值相等)。 以下示例演示如何测试相等性。

Dim prod3 = New With {Key .Name = "paperclips", Key .Price = 1.29}
Dim prod4 = New With {Key .Name = "paperclips", Key .Price = 1.29}
' The following line displays True, because prod3 and prod4 are
' instances of the same anonymous type, and the values of their
' key properties are equal.
Console.WriteLine(prod3.Equals(prod4))

Dim prod5 = New With {Key .Name = "paperclips", Key .Price = 1.29}
Dim prod6 = New With {Key .Name = "paperclips", Key .Price = 1.29,
                      .OnHand = 423}
' The following line displays False, because prod5 and prod6 do not 
' have the same properties.
Console.WriteLine(prod5.Equals(prod6))

Dim prod7 = New With {Key .Name = "paperclips", Key .Price = 1.29,
                      .OnHand = 24}
Dim prod8 = New With {Key .Name = "paperclips", Key .Price = 1.29,
                      .OnHand = 423}
' The following line displays True, because prod7 and prod8 are
' instances of the same anonymous type, and the values of their
' key properties are equal. The equality check does not compare the
' values of the non-key field.
Console.WriteLine(prod7.Equals(prod8))

只读值

无法更改键属性的值。 例如上例中的 prod8NamePrice 字段是 read-only,但 OnHand 可以更改。

' The following statement will not compile, because Name is a key
' property and its value cannot be changed.
' prod8.Name = "clamps"

' OnHand is not a Key property. Its value can be changed.
prod8.OnHand = 22

查询表达式中的匿名类型

查询表达式并不总是需要创建匿名类型。 如果可能,它们会使用现有类型来保存列数据。 当查询返回数据源中的所有记录,或只返回一条记录中的一个字段时,就会发生这种情况。 在下面的代码示例中,customersCustomer 类的对象的集合。 类具有许多属性,你可在查询结果中按任意顺序包含一个或多个属性。 在前两个示例中,不需要匿名类型,因为查询选择命名类型的元素:

  • custs1 包含字符串集合,因为 cust.Name 是字符串。

    Dim custs1 = From cust In customers
                 Select cust.Name
    
  • custs2 包含 Customer 对象集合,因为 customers 中的每个元素都是 Customer 对象,并且查询选择了整个元素。

    Dim custs2 = From cust In customers
                 Select cust
    

但是,相应的命名类型并非始终可用。 最好选择将客户名称和地址用作一个目的、将客户 ID 号和位置来用作另一个目的,将客户姓名、地址和订单历史记录用作第三个目的。 利用匿名类型,可按任意顺序选择任何属性组合,而无需首先声明新的命名类型来保存结果。 而编译器则为每个属性的编译创建匿名类型。 下面的查询只从 customers 的每个 Customer 对象中选择客户的姓名和 ID 号。 因此,编译器将创建仅包含这两个属性的匿名类型。

Dim custs3 = From cust In customers
             Select cust.Name, cust.ID

匿名类型中属性的名称和数据类型均取自 Selectcust.Namecust.ID 的参数。 由查询创建的匿名类型中的属性始终是键属性。 当在下面的 For Each 循环中执行 custs3 时,结果是具有两个键属性(NameID)的匿名类型的实例集合。

For Each selectedCust In custs3
    Console.WriteLine(selectedCust.ID & ": " & selectedCust.Name)
Next

custs3 表示的集合中的元素是强类型,你可使用 IntelliSense 来浏览可用的属性并验证它们的类型。

有关详细信息,请参阅Visual Basic 中的 LINQ 简介

确定是否使用匿名类型

在将对象创建为匿名类的实例之前,请考虑这是否是最佳选择。 例如,如果要创建包含相关数据的临时对象,并且不需要完整类可能包含的其他字段和方法,则匿名类型是一个不错的解决方案。 如果希望为每个声明选择不同的属性,或者要更改属性的顺序,则匿名类型也很方便。 但如果项目包括多个具有相同属性的对象,则可以通过将命名类型与类构造函数一起使用来更轻松地声明它们。 例如,如果使用适当的构造函数,声明 Product 类的多个实例比声明匿名类型的多个实例要容易得多。

' Declaring instances of a named type.
Dim firstProd1 As New Product("paperclips", 1.29)
Dim secondProd1 As New Product("desklamp", 28.99)
Dim thirdProd1 As New Product("stapler", 5.09)

' Declaring instances of an anonymous type.
Dim firstProd2 = New With {Key .Name = "paperclips", Key .Price = 1.29}
Dim secondProd2 = New With {Key .Name = "desklamp", Key .Price = 28.99}
Dim thirdProd2 = New With {Key .Name = "stapler", Key .Price = 5.09}

命名类型的另一个优点是,编译器可以捕获属性名称的意外拼写错误。 在前面的示例中,firstProd2secondProd2thirdProd2 是属于相同匿名类型的实例。 但如果不小心用以下某种方式声明 thirdProd2,那么它的类型将不同于 firstProd2secondProd2 的类型。

' Dim thirdProd2 = New With {Key .Name = "stapler", Key .Price = 5.09}
' Dim thirdProd2 = New With {Key .Name = "stapler", Key .Price = "5.09"}
' Dim thirdProd2 = New With {Key .Name = "stapler", .Price = 5.09}

更重要的是,使用匿名类型时存在一些不适用于命名类型的限制。 firstProd2secondProd2thirdProd2 是属于相同匿名类型的实例。 但是,共享匿名类型的名称不可用,并且不能出现在代码中需要类型名称的位置。 例如,匿名类型不能用于定义方法签名、声明其他变量或字段,或在任何类型声明中使用。 因此,在必须跨方法共享信息时,匿名类型并不适用。

匿名类型定义

为了响应匿名类型实例的声明,编译器将创建一个包含指定属性的新类定义。

如果匿名类型至少包含一个键属性,则定义将替代从 Object 继承的三个成员:EqualsGetHashCodeToString。 为测试相等性并确定哈希代码值而生成的代码仅考虑键属性。 如果匿名类型不包含键属性,则仅替代 ToString。 匿名类型的显式命名属性不能与这些生成的方法发生冲突。 也就是说,不能使用 .Equals.GetHashCode.ToString 来命名属性。

至少具有一个键属性的匿名类型定义也将实现 System.IEquatable<T> 接口,其中 T 是匿名类型的类型。

若要详细了解编译器创建的代码以及替代方法的功能,请参阅匿名类型定义

请参阅