有時候,例如在某個函式裡面,我們會臨時需要一個簡單的類別來儲存一些簡單資料,但又不想為了這個簡單的需求另外定義一個類別,此時便可使用 C# 的匿名型別(anonymous type)。
參考以下範例:
執行結果如下:
「OK! 既然從程式的執行結果已經知道實際型別,那我就直接用這個型別名稱來宣告變數就好啦!」你說。
不行!你如果直接在程式中使用 <>f__AnonymousType0`2 來宣告變數,編譯器會看不懂。
「那如果要宣告兩個相同匿名型別的變數怎麼辦?」
如果把範例程式碼改成這樣:
你會發現 emp1 和 emp2 的實際型別都一樣。基於效率考量,編譯器會重複使用具有相同參數個數與參數名稱的匿名型別,而不會每碰到一個匿名型別的宣告就產生一個新的泛型類別。
不過,如果你將 emp2 的宣告改成這樣:
emp1 的實際型別是: <>f__AnonymousType0`2[System.String,System.DateTime]
emp2 的實際型別是: <>f__AnonymousType1`2[System.String,System.String]
那麼,如果參數個數相同,參數名稱相同,但型別不同呢?這種情況,編譯器仍會重複使用先前產生的泛型類別。這些編譯器的處理規則對我們來說可能太過細節,不過,匿名型別還有另一種定義方式可能需要瞭解一下:projection initializers(投射式初始值設定)(路人:好彆扭啊!還是直接寫英文吧!)
Projection Initializers
下面是一個 projection initializers 的範例:
注意事項
使用匿名型別時,請注意幾件事:
參考以下範例:
private void Foo() { var emp = new { Name = "Michael", Birthday = new DateTime(1971, 1, 1) }; Console.WriteLine("Employee name: " + emp.Name); Console.WriteLine("Birthday: " + emp.Birthday.ToString()); Console.WriteLine("實際的型別是: " + emp.GetType()); }這個範例使用了隱含型別 var 來宣告區域變數 emp,這是必須的,因為我們使用了匿名型別。這表示從 new 運算子之後的大括弧包住的部分,會由編譯器產生一個類別定義。當然該類別的名稱也是由編譯器決定,所以我們在寫程式時便無法得知實際的類別名稱,自然就得用 var 來宣告變數了。
執行結果如下:
Employee name: Michael Birthday: 1971/1/1 上午 12:00:00 實際的型別是: <>f__AnonymousType0`2[System.String,System.DateTime]範例程式的最後一行是把匿名型別的實際型別名稱秀出來。注意此型別是個泛型類別(generic class),它有兩個參數型別。
「OK! 既然從程式的執行結果已經知道實際型別,那我就直接用這個型別名稱來宣告變數就好啦!」你說。
不行!你如果直接在程式中使用 <>f__AnonymousType0`2 來宣告變數,編譯器會看不懂。
「那如果要宣告兩個相同匿名型別的變數怎麼辦?」
如果把範例程式碼改成這樣:
private void Foo() { var emp1 = new { Name = "Michael", Birthday = new DateTime(1971, 1, 1) }; var emp2 = new { Name = "John", Birthday = new DateTime(1981, 12, 31) }; Console.WriteLine("emp1 的實際型別是: " + emp1.GetType()); Console.WriteLine("emp2 的實際型別是: " + emp2.GetType()); }
你會發現 emp1 和 emp2 的實際型別都一樣。基於效率考量,編譯器會重複使用具有相同參數個數與參數名稱的匿名型別,而不會每碰到一個匿名型別的宣告就產生一個新的泛型類別。
不過,如果你將 emp2 的宣告改成這樣:
var emp2 = new { Name = "John", Birth = new DateTime(1981, 12, 31) };由於參數名稱不同,編譯器會產生另一個泛型類別。執行結果如下:
emp1 的實際型別是: <>f__AnonymousType0`2[System.String,System.DateTime]
emp2 的實際型別是: <>f__AnonymousType1`2[System.String,System.String]
那麼,如果參數個數相同,參數名稱相同,但型別不同呢?這種情況,編譯器仍會重複使用先前產生的泛型類別。這些編譯器的處理規則對我們來說可能太過細節,不過,匿名型別還有另一種定義方式可能需要瞭解一下:projection initializers(投射式初始值設定)(路人:好彆扭啊!還是直接寫英文吧!)
Projection Initializers
下面是一個 projection initializers 的範例:
public class EmpInfo { private string name; private DateTime birthday; public EmpInfo(string aName, DateTime aBirthday) { name = aName; birthday = aBirthday; } public string Name { get { return name; } set { name = value; } } public DateTime Birthday { get { return birthday; } set { birthday = value; } } } class Program { static void Main(string[] args) { Console.WriteLine("示範 projection initializers 的匿名型別寫法。"); EmpInfo emp = new EmpInfo("Michael", new DateTime(1971, 1, 1)); var emp1 = new { emp.Name, emp.Birthday }; string name = "John"; int age = 20; var emp2 = new { name, age }; Console.WriteLine("emp1.Name = " + emp1.Name); Console.WriteLine("emp2.name = " + emp2.name); Console.WriteLine("emp1 的實際型別: " + emp1.GetType()); Console.WriteLine("emp2 的實際型別: " + emp2.GetType()); } }此範例包含兩種 projection initializer 的寫法,一種是利用事先定義的類別 EmpInfo 的屬性名稱,也就是說,編譯器在產生泛型類別時,會用 EmpInfo 的屬性名稱來當作參數名稱。另一種則是直接帶入區域變數名稱,即範例中以英文小寫宣告的變數 name 和 age。
注意事項
使用匿名型別時,請注意幾件事:
- 編譯器為匿名型別所產生的泛型類別並未實作 IDisposable 介面,因此,你應該避免在匿名型別中存放可丟棄的(disposable)物件。
- 一般而言,匿名型別的物件不應該儲存在 List 這類物件串列中。要是你這麼做的話,到時候取出物件時,你怎麼知道要將它轉成甚麼型別呢?
- 承上,匿名型別的物件也不能當作參數傳遞。因為,如果硬要這麼做,參數型別勢必得宣告為 object,這當然可以通過編譯。可是同樣的問題,這個參數將來要如何轉型呢?
不好意思,第一段code與第二段code好像一模一樣喔!
回覆刪除哎呀!程式碼貼錯了,被抓包 @_@
回覆刪除多謝告知 :)