匿名类型 (Visual Basic)

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

以下示例将 变量声明并创建为具有两个属性( 和 )的匿名 product 类型的 Name 实例 Price

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

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

在下面的示例中, products 是产品对象的列表,其中每个对象具有许多属性。 变量保存查询的定义,该查询在执行时返回具有两个属性 的匿名类型的实例 namePriceQuery Name 的集合: 和 Price

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

变量保存查询的定义,该查询在执行时返回具有两个属性 的匿名类型的实例 nameQuantityQuery Name 的集合: 和 OnHand

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))

Read-Only值

无法更改键属性的值。 例如,在 prod8 上一示例中, 和 Name Price 字段为 read-onlyOnHand 但可进行更改。

' 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

查询表达式中的匿名类型

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

  • 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 号和位置,以及第三个客户名称、地址和订单历史记录。 匿名类型允许按任意顺序选择任何属性组合,而无需首先声明新的命名类型来保存结果。 相反,编译器会为每个属性编译创建匿名类型。 以下查询仅从 中的每个 对象中选择客户的姓名和 ID Customercustomers 。 因此,编译器将创建仅包含这两个属性的匿名类型。

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

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

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

由 表示的集合中的元素是强类型元素,可以使用 IntelliSense 浏览可用属性并 custs3 验证其类型。

有关详细信息,请参阅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}

命名类型的另一个优点是,编译器可以捕获属性名称的意外错误。 在以上示例中, firstProd2 、 和 旨在 secondProd2 thirdProd2 成为同一匿名类型的实例。 但是,如果意外以下列方式之一声明,则其类型 thirdProd2 将不同于 和 firstProd2 的类型 secondProd2

' 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 Equals GetHashCode :、 和 ToString 。 为测试相等性并确定哈希代码值而生成的代码仅考虑键属性。 如果匿名类型不包含键属性,则仅 ToString 重写 。 匿名类型的显式命名属性不能与这些生成的方法冲突。 也就是说,不能使用 、 .Equals .GetHashCode.ToString 来命名属性。

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

有关编译器创建的代码以及重写方法的功能详细信息,请参阅 匿名类型定义

另请参阅