匿名類型是 C# 3.0 為支援 LINQ 查詢投影而引入的核心特性。它允許在無具名類別定義的情況下,直接建立擁有強型別屬性的物件。本文從使用語法、編譯器生成機制、IL 驗證、型別身分規則全方位解析。
var product = new { Name = "筆電", Price = 25000, InStock = true };
Console.WriteLine($"{product.Name}: {productPrice:C}"); // 強型別 IntelliSense 支援
var 配合 new { ... } 物件初始化器get,無 set)編譯器在編譯期自動產生一個內部類別(名稱如 <Projection>f__0),包含:
<Name>k__BackingField)get 存取子)Equals、GetHashCode、ToString(基於所有屬性值)等價原始碼:
class __Anonymous1
{
private string _name;
private int _price;
private bool _inStock;
public string Name { get { return _name; } }
public int Price { get { return _price; } }
public bool InStock { get { return _inStock; } }
public __Anonymous1(string name, int price, bool inStock)
{
_name = name; _price = price; _inStock = inStock;
}
public override bool Equals(object obj) { /* 比較所有屬性 */ }
public override int GetHashCode() { /* 結合所有屬性雜湊 */ }
public override string ToString() { /* 格式化所有屬性 */ }
}
使用 ILDASM 開啟編譯後組件,可見:
<>、f__),原始碼無法直接參考.property 定義,含 .get 方法initonly(唯讀)同一組件內,若匿名類型初始化器具備:
編譯器會重用同一個產生型別。範例:
var a = new { Name = "A", Price = 10 };
var b = new { Name = "B", Price = 20 }; // 同型別
var c = new { Price = 10, Name = "A" }; // 不同型別(順序不同)
var d = new { Name = "A", Price = 10L }; // 不同型別(型別不同:long vs int)
a = b; // 合法
// a = c; // 編譯錯誤
不同組件編譯的匿名類型永不相同,即使結構完全一致。因此:
| 場景 | 建議 |
|---|---|
| LINQ select 投影 | ✅ 最佳選擇 |
| 區域臨時資料封裝 | ✅ 適合 |
| 單元測試資料建構 | ✅ 方便 |
| 方法回傳值 | ❌ 使用 Tuple/record/具名類別 |
| 公用介面參數/回傳 | ❌ 使用具名類別 |
| 需序列化傳輸 | ❌ 使用 DTO/record |