匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。 类型名称由编译器生成,在源代码级别不可用。 每个属性的类型由编译器推断。
可结合使用 new 运算符和对象初始值设定项创建匿名类型。 有关对象初始值设定项的详细信息,请参阅对象和集合初始值设定项。
以下示例显示了用两个名为 Amount 和 Message 的属性进行初始化的匿名类型。
var v = new { Amount = 108, Message = "Hello" };
// Rest the mouse pointer over v.Amount and v.Message in the following
// statement to verify that their inferred types are int and string.
Console.WriteLine(v.Amount + v.Message);
匿名类型通常用在查询表达式的 select 子句中,以便返回源序列中每个对象的属性子集。 有关查询的详细信息,请参阅C# 中的 LINQ。
匿名类型包含一个或多个公共只读属性。 包含其他种类的类成员(如方法或事件)为无效。 用于初始化属性的表达式不能是 null匿名函数或指针类型。
最常见的方案是用其他类型的属性初始化匿名类型。 在下面的示例中,假定名为 Product 的类存在。 类 Product 包括 Color 和 Price 属性,以及你不感兴趣的其他属性:
class Product
{
public string? Color { get; set; }
public decimal Price { get; set; }
public string? Name { get; set; }
public string? Category { get; set; }
public string? Size { get; set; }
}
匿名类型声明以 new 关键字开始。 声明初始化了一个只使用 Product 的两个属性的新类型。 使用匿名类型会导致在查询中返回的数据量变少。
如果你没有在匿名类型中指定成员名称,编译器会为匿名类型成员指定与用于初始化这些成员的属性相同的名称。 需要为使用表达式初始化的属性提供名称,如下面的示例所示。 在下面示例中,匿名类型的属性名称都为 ColorPrice 和 。 这些实例是类型集合中的productsProduct项:
var productQuery =
from prod in products
select new { prod.Color, prod.Price };
foreach (var v in productQuery)
{
Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}
投影初始值设定项
匿名类型支持 投影初始值设定项,这允许直接使用局部变量或参数,而无需显式指定成员名称。 编译器从变量名称推断成员名称。 以下示例演示了此简化的语法:
// Explicit member names.
var personExplicit = new { FirstName = "Kyle", LastName = "Mit" };
// Projection initializers (inferred member names).
var firstName = "Kyle";
var lastName = "Mit";
var personInferred = new { firstName, lastName };
// Both create equivalent anonymous types with the same property names.
Console.WriteLine($"Explicit: {personExplicit.FirstName} {personExplicit.LastName}");
Console.WriteLine($"Inferred: {personInferred.firstName} {personInferred.lastName}");
创建具有许多属性的匿名类型时,此简化的语法非常有用:
var title = "Software Engineer";
var department = "Engineering";
var salary = 75000;
// Using projection initializers.
var employee = new { title, department, salary };
// Equivalent to explicit syntax:
// var employee = new { title = title, department = department, salary = salary };
Console.WriteLine($"Title: {employee.title}, Department: {employee.department}, Salary: {employee.salary}");
以下情况下不会推断成员名称:
- 候选名称是同一匿名类型(显式或隐式)中另一个属性成员的副本。
- 候选名称不是有效的标识符(例如,它包含空格或特殊字符)。
在这些情况下,必须显式指定成员名称。
提示
可以使用 .NET 样式规则 IDE0037 强制执行是首选推断成员名称还是显式成员名称。
还可以按另一类型的对象定义字段:类、结构,甚至另一个匿名类型。 它通过使用保存此对象的变量完成,就像在以下示例中一样,使用已实例化的用户定义类型创建两个匿名类型。 在这两种情况下,匿名类型 `product` 的 `shipment` 和 `shipmentWithBonus` 字段都是 `Product` 类型,并包含每个字段的默认值。
bonus字段由编译器创建的匿名类型。
var product = new Product();
var bonus = new { note = "You won!" };
var shipment = new { address = "Nowhere St.", product };
var shipmentWithBonus = new { address = "Somewhere St.", product, bonus };
通常,当使用匿名类型来初始化变量时,可以通过使用 var 将变量作为隐式键入的本地变量来进行声明。 无法在变量声明中指定类型名称,因为只有编译器有权访问匿名类型的基础名称。 有关 var 的详细信息,请参阅隐式类型本地变量。
可通过将隐式键入的本地变量与隐式键入的数组相结合创建匿名键入的元素的数组,如下面的示例所示。
var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};
匿名类型是 class 直接派生自 object的类型,不能强制转换为除任何类型之外 object的任何类型。 编译器为每个匿名类型提供一个名称,尽管应用程序无法访问它。 从公共语言运行时的角度来看,匿名类型与任何其他引用类型没有什么不同。
如果程序集中的两个或多个匿名对象初始值指定了属性序列,这些属性采用相同顺序且具有相同的名称和类型,则编译器将对象视为相同类型的实例。 它们共享同一编译器生成的类型信息。
匿名类型支持采用 with 表达式形式的非破坏性修改。 这使你能够创建匿名类型的新实例,其中一个或多个属性具有新值:
var apple = new { Item = "apples", Price = 1.35 };
var onSale = apple with { Price = 0.79 };
Console.WriteLine(apple);
Console.WriteLine(onSale);
不能将字段、属性、事件或方法的返回类型声明为具有匿名类型。 同样,不能将方法、属性、构造函数或索引器的正式参数声明为具有匿名类型。 要将匿名类型或包含匿名类型的集合作为参数传递给某一方法,可将参数作为类型 object 进行声明。 但是,对匿名类型使用 object 违背了强类型的目的。 如果必须存储查询结果或者必须将查询结果传递到方法边界外部,请考虑使用普通的命名结构或类而不是匿名类型。
由于匿名类型上的 Equals 和 GetHashCode 方法是根据方法属性的 Equals 和 GetHashCode 定义的,因此仅当同一匿名类型的两个实例的所有属性都相等时,这两个实例才相等。
注意
匿名类型的 访问级别 为 internal。 因此,在不同程序集中定义的两个匿名类型不是同一类型。
因此,当在不同的程序集中进行定义时,匿名类型的实例不能彼此相等,即使其所有属性都相等。
匿名类型确实会重写 ToString 方法,将用大括号括起来的每个属性的名称和 ToString 输出连接起来。
var v = new { Title = "Hello", Age = 24 };
Console.WriteLine(v.ToString()); // "{ Title = Hello, Age = 24 }"