匿名類型是 C# 3.0 引入的編譯器生成的唯讀類類型,通過對象初始化器語法隱式聲明,無需預先定義 class。本質上是編譯器根據屬性名、類型、順序自動合成的不可變引用類型,專為臨時數據聚合、LINQ 投影、跨層傳輸設計。
var person = new { Name = "Tom", Age = 25, Skills = new[] {"C#", "SQL"} };
// 編譯器生成的內部類型(名稱不可預測,如 <>f__AnonymousType0`2)
[CompilerGenerated]
internal sealed class <>f__AnonymousType0
{
private readonly TName _Name;
private readonly TAge _Age;
public TName Name { get { return _Name; } }
public TAge Age { get { return _Age; } }
public <>f__AnonymousType0(TName name, TAge age)
{ _Name = name; _Age = age; }
// 重寫 Equals/GetHashCode/ToString:基於所有屬性值比較
public override bool Equals(object obj) { ... }
public override int GetHashCode() { ... }
public override string ToString() { ... }
}
Equals/GetHashCode 基於所有屬性值,而非引用
var p1 = new { Name = "A", Age = 1 };
var p2 = new { Name = "B", Age = 2 };
var p3 = new { Age = 1, Name = "C" }; // 順序不同 → 不同類型
Console.WriteLine(p1.GetType() == p2.GetType()); // True
Console.WriteLine(p1.GetType() == p3.GetType()); // False
p1 = p2; // 合法:同類型
// p1 = p3; // 編譯錯誤:類型不匹配
匿名類型為內部類型,無法作為方法參數/返回類型聲明。三種應對策略:
void Print(object anon)
{
var name = anon.GetType().GetProperty("Name")?.GetValue(anon);
Console.WriteLine(name);
}
void Process(T anon, Action handler) where T : class
{
handler(anon);
}
// 調用:編譯器推斷 T 為匿名類型
Process(new { Id = 1, Name = "Test" }, x => Console.WriteLine(x.Name));
// 定義明確類型
public record PersonDto(string Name, int Age);
// 投影轉換
var dto = people.Select(p => new PersonDto(p.Name, p.Age)).ToList();
// 或手動
var anon = new { Name = "Tom", Age = 25 };
var dto = new PersonDto(anon.Name, anon.Age);
select new { c.Name, c.Orders.Count }var options = new { Timeout = 30, Retry = 3 };匿名類型是「編譯器幫你寫樣板代碼」的極致體現:聲明即定義,用完即走。它讓 LINQ 投影、臨時聚合變得極其簡潔,但切勿作為公共 API 契約。生產代碼中,邊界處請映射為具體 Record/DTO,內部邏輯盡情享受匿名類型的輕量優雅。