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
是具有两个属性的匿名类型的实例: Name
和 Price
。
' 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))
只读值
无法更改键属性的值。 例如,在prod8
的前一个示例中,Name
字段和Price
字段都是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
从查询表达式产生的匿名类型
查询表达式并不总是需要创建匿名类型。 如果可能,它们使用现有类型来保存列数据。 当查询从数据源返回整个记录或仅从每个记录返回一个字段时,将发生这种情况。 在以下代码示例中, 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 号和位置,以及客户姓名、地址以及第三个订单历史记录。 匿名类型使你可以按任意顺序选择属性的任意组合,而无需首先声明新的命名类型来保存结果。 相反,编译器会为每个属性编译创建一个匿名类型。 以下查询仅从每个 Customer
对象 customers
中选择客户的名称和 ID 号。 因此,编译器将创建一个仅包含这两个属性的匿名类型。
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
所 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}
命名类型的另一个优点是编译器可以捕获属性名称的错误输入。 在前面的示例中,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}
更重要的是,匿名类型的使用有一些限制,而这些限制不适用于命名类型的实例。
firstProd2
, secondProd2
是 thirdProd2
同一匿名类型的实例。 但是,共享匿名类型的名称不可用,不能出现在代码中需要类型名称的地方。 例如,匿名类型不能用于定义方法签名、声明另一个变量或字段,或者在任何类型声明中。 因此,当必须跨方法共享信息时,匿名类型不适用。
匿名类型定义
为了响应匿名类型的实例的声明,编译器将创建一个包含指定属性的新类定义。
如果匿名类型至少包含一个键属性,则定义将替代继承自 Object: Equals、 GetHashCode和 ToString的三个成员。 为测试相等性和确定哈希代码值而生成的代码仅考虑键属性。 如果匿名类型不包含键属性,则只有 ToString 被重写。 匿名类型的显式命名属性不能与这些生成的方法冲突。 也就是说,不能使用.Equals
或.GetHashCode
.ToString
命名属性。
至少有一个键属性的匿名类型定义也实现 System.IEquatable<T> 接口,其中 T
是匿名类型的类型。
有关编译器创建的代码及其与重载方法功能的关系的详细信息,请参阅 匿名类型定义。