C# 匿名类型
匿名类型提供了一种快捷方式,将一组只读属性封装到单个对象中,无需先显式定义命名类型。类型名称由编译器在编译时生成,源代码中无法直接访问。
- 声明与推断:一行代码创建数据容器
- LINQ 投影:匿名类型的主要阵地
- 值相等性:相同属性自动相等
- 匿名类型 vs 元组:什么时候选哪个
一、基本声明
使用 new 运算符与对象初始值设定项结合来创建。
csharp
var person = new { Name = "Alice", Age = 30 };
Console.WriteLine($"{person.Name} is {person.Age} years old.");
// 输出:Alice is 30 years old.
推断的属性名称
当使用变量或成员访问表达式初始化时,编译器自动推断属性名称:
csharp
string productName = "Laptop";
decimal price = 999.99m;
var product = new { productName, price };
Console.WriteLine($"{product.productName}: {product.price:C}");
// 输出:Laptop: $999.99
代码解析: 不需要写 Name = productName------编译器从变量名推断属性名为 productName。
必须使用 var
由于类型名称不可见,必须 使用 var 来声明:
csharp
var person = new { Name = "Alice", Age = 30 };
// 不能写成:AnonymousType person = ... // 类型名不存在
二、在 LINQ 查询中使用
匿名类型最常见的用途是 LINQ 的 select 子句中的投影------只提取需要的属性子集。
csharp
var words = new[] { "apple", "blueberry", "cherry" };
var results = words.Select(w => new { Word = w, Length = w.Length });
foreach (var item in results)
Console.WriteLine($"{item.Word} has {item.Length} letters.");
// 输出:
// apple has 5 letters.
// blueberry has 9 letters.
// cherry has 6 letters.
划重点: 这是匿名类型的核心场景。你不需要为每个查询结果定义一个 DTO 类------一行代码搞定。
三、相等性
具有相同属性名称和类型 的两个匿名类型实例会共享编译器生成的同一类型。编译器自动重写 Equals 和 GetHashCode,基于属性值比较:
csharp
var a = new { Name = "Alice", Age = 30 };
var b = new { Name = "Alice", Age = 30 };
var c = new { Name = "Bob", Age = 25 };
Console.WriteLine(a.Equals(b)); // True ------ 相同类型、相同值
Console.WriteLine(a.Equals(c)); // False ------ 值不同
注意: 属性名称相同但顺序不同的匿名类型被视为不同类型------编译器按照属性名和顺序生成类型。
四、嵌套匿名类型
匿名类型可以包含其他匿名类型:
csharp
var order = new
{
OrderId = 1,
Customer = new { Name = "Alice", City = "Seattle" },
Total = 150.00m
};
Console.WriteLine($"Order {order.OrderId} for {order.Customer.Name} in {order.Customer.City}");
// 输出:Order 1 for Alice in Seattle
五、特性总结
| 特性 | 值 |
|---|---|
| 编译器生成类型 | internal sealed class,派生自 Object |
| 属性可访问性 | 所有属性 public 且只读 |
| 支持的结构 | with 表达式、基于值的 Equals/GetHashCode/ToString |
| 表达式树 | ✅ 支持(元组不支持) |
局限性
- 不能作为方法返回类型、方法参数或字段类型(无法命名)
- 作用域限定在声明它们的方法内
- 不能添加方法、事件或自定义运算符
- 属性始终只读
六、匿名类型 vs 元组
| 维度 | 匿名类型 | 元组 |
|---|---|---|
| 类型类别 | 引用类型 | 值类型 |
| 作用域 | 方法内 | 可跨方法 |
| 表达式树 | ✅ 支持 | ❌ 不支持 |
| 解构 | ❌ | ✅ |
| 属性可写性 | 只读 | 可读写(默认) |
| 语法简洁度 | new { ... } |
(...) |
| 推荐场景 | LINQ to SQL / 表达式树 | 新代码的通用数据分组 |
决策建议: 对于大多数新代码,优先使用元组 ------性能更好、语法更简洁、支持解构。只在与表达式树(Entity Framework 的 Select 投影、动态 LINQ 查询)打交道时,匿名类型仍是不可替代的。
最后
匿名类型像是一个"方法内部的 DTO"。在 LINQ 查询中投影中间结果时非常方便,但一出了方法边界就无能为力。新代码中元组已经替代了匿名类型的大部分场景,把它当作 LINQ 投影的专用工具就好。