[轉]c#匿名類

來源:互聯網
上載者:User

標籤:

首先讓我們看一個例子, 假設我們並沒有Person類, 並且我們關心的屬性只有Name和Age. 下面的代碼示範了我們如何在沒有宣告類型的情況下來構建一個對象的:

   1: var tom = new { Name = Tom, Age = 4 };
   2: var holly = new { Name = Holly, Age = 31 };
   3: var jon = new { Name = Jon, Age = 31 };
   4: Console.WriteLine({0} is {1} years old, jon.Name, jon.Age);

可以看到, 初始化一個匿名類與我們之前提到的對象初始化器非常相似——區別僅僅是在new和開始的大括弧之間的類型名稱沒有了. 我們正在使用隱式的局部變數, 因為這是我們的唯一選擇——我們沒有任何的類型名可以用於聲明該變數. 從上面的最後一行代碼你可以看到, 類型用於Name和Age屬性, 他們可以被讀取並且其值就是在隱式類型初始化器中被賦予的值, 因此, 該輸出將會是Jon is 31 years old. 屬性將會有與初始化器內的運算式一樣的類型——Name是string類型, Age是int類型. 和對象初始化器一樣的是, 方法和構造器也可以被用於隱式對象初始化器中, 用於做任何你想做的是事情.

現在你知道為什麼隱式類型數組為什麼這麼重要了. 假設我們想建立包含整個家庭成員的數組, 然後迭代算出總的年齡. 以下的代碼將會完成這個工作——同時也示範了其他一些有趣的關於匿名型別的特性.

   1: var family = new[]
   2: {
   3:     new { Name = Holly, Age = 31 },
   4:     new { Name = Jon, Age = 31 },
   5:     new { Name = Tom, Age = 4 },
   6:     new { Name = Robin, Age = 1 },
   7:     new { Name = William, Age = 1 }
   8: };
   9: int totalAge = 0;
  10: foreach (var person in family)
  11: {
  12:     totalAge += person.Age;
  13: }
  14: Console.WriteLine(Total age: {0}, totalAge);

從前面瞭解過的隱式類型數組來看, 我們可以推論出一個重要的事情: 所有的家庭成員類型都是一樣的. 因為如果他們使用匿名型別初始化器建立的都是一個新的類型, 那麼明顯數群組類型不可能被正確的聲明. 在一個給定的Assembly中, 如果兩個匿名型別擁有同樣數量的屬性, 並且他們有相同的名字和類型, 以及相同的出現順序, 那麼編譯器將會把他們當成同一個類型. 換句話說, 如果我們把其中一個的Name和Age屬性調換一下, 編譯器將會產生一個新的匿名型別——同樣的,如果我們在新行中引入一個而外的屬性, 或者使用long類型代替Age的int類型, 這些統統會導致編譯器產生一個新的匿名型別.

實現細節:如果你曾經看過匿名類的IL代碼, 你應該知道即使兩個匿名對象初始化器擁有同樣的屬性名稱和出現出現, 但卻使用不同的類型, 那麼編譯器會產生兩個不同的類型, 而它們實際上是通過一個單獨的泛型類來產生的. 該泛型類是參數化的, 但其封閉的構造類型將會因為給與不同初始化器的型別參數不同而不同.

我們可以使用foreach運算式應用於上述的數組, 就像我們用於集合中的一樣. 類型是由編譯器推斷的, person的類型也就是數組的類型. 再次提一下, 我們可以將同樣的變數用於不同的執行個體中, 因為它們全部都擁有相同的類型.

上述的代碼同樣驗證了Age屬性是強型別的int, 否則我們試圖計算age總和將會導致錯誤. 編譯器瞭解匿名型別, VS2008更是通過tooltip的方式提供更多的資訊. 現在我們已經瞭解了足夠的關於匿名型別資訊, 接下來讓我們看看編譯器實際上為我們做的工作.

匿名型別的成員

匿名型別是由編譯器建立並其包含在編譯後的Assembly當中, 其方式與匿名方法和iterator block的建立方式是一致的. CRL把它們都當成普通的類型, 實際上他們就是普通的類型——如果你將其從匿名型別更改成為一個普通類型, 並且手工編寫所有行為的代碼, 我們不會看到有任何的改變. 匿名型別包含以下的成員:

一個負責所有初始化值的構造器, 其參數將會是與匿名對象初始化器當中出現的順序和類型一致, 同樣名稱也是一樣的.
公用唯讀屬性
私人的唯讀欄位, 用於支援屬性
重載了Equals, GetHashCode和ToString
這就是全部了, 沒有實現任何借口, 沒有複製和序列化能力——僅僅是一個構造器, 一些屬性和幾個來自於object的平常的方法.

構造器和屬性完成都是一些顯而易見的事情. 兩個來自於相同匿名型別的實施是否相等, 通過輪流比較所有屬性類型的Equals方法來決定.hash代碼產生也是一類似的, 通過調用每個屬性的GetHashCode然後再合并結果. 將多個hash code合并成為一個組合, 其方法並沒有被指定, 因此你不能在你的代碼中依靠它——你唯一有信心的就是, 兩個相等的對象執行個體必然返回相同的hash值, 而兩個不等的執行個體可能會返回不等的hash值. 當然所有這些也只有當屬性類型實現的GetHashCode和Equals也遵循常規規則的時候才有效.

注意因為屬性是唯讀, 因此匿名類是不可變的, 因此屬性的類型也是不可變的. 由於這個不可變性, 你不用擔心屬性在賦值後會被改變, 跨線程自然也沒有問題.

發散性初始化器(projection initializers)

目前我們看到的匿名對象初始化器中我們一直使用簡單的name / value對——Name=Jon, Age=31, 雖然有時候我們確實是這麼做, 但是更多時候在真實的編程中, 我們可能會從一個已有的對象中來拷貝屬性值. 有時我們會通過一些方式來讀取值, 不過更經常的是拷貝就足夠了.

沒有LINQ, 要給出令人信服的例子有點困難, 不過讓我們回到我們的Person類, 假設我們有一個很好的理由我們想將一個Person執行個體的集合轉換到另外一個類似的集合, 其元素包含有一個name和一個flag指示該person是否是一個成年人. 給定一個合適的變數, 我們可以使用下面的代碼:

   1: new { Name = person.Name, IsAdult = (person.Age >= 18) }

該代碼當然能夠工作, 如果僅僅是設定一個單一的name屬性使用此方法當然還不算笨拙——但如果你要拷貝很多的屬性, 你應該考慮其他的嘗試. C# 3提供了一個捷徑: 如果你沒有指定屬性名稱, 而僅僅使用運算式去對值進行評估, 那麼編譯器將會用運算式的最後一部分作為屬性名稱. 這被稱為發散性初始化器. 這意味著我們可以將上面的代碼改寫為:

   1: new { person.Name, IsAdult = (person.Age >= 18) }

將一個匿名對象初始化器變為一個發散性初始化器是很常見的——通常當你想從一個對象拷貝一些屬性到另外一個對象的時候發生, 而經常作為join操作的一部分. 一下顯示了完整的代碼, 使用了List.ConvertAll方法和匿名代理:

   1: List<Person> family = new List<Person>
   2: {
   3:     new Person {Name=Holly, Age=31},
   4:     new Person {Name=Jon, Age=31},
   5:     new Person {Name=Tom, Age=4},
   6:     new Person {Name=Robin, Age=1},
   7:     new Person {Name=William, Age=1}
   8: };
   9: var converted = family.ConvertAll(delegate(Person person)
  10: {
  11:     return new { person.Name, IsAdult = (person.Age >= 18) }; }
  12: );
  13: foreach (var person in converted)
  14: {
  15:     Console.WriteLine({0} is an adult? {1},
  16:     person.Name, person.IsAdult);
  17: }

上述的代碼初始了使用了發散性初始化器, 我們還展示了匿名方法和代理類型推論的價值, 沒有它們, 我們無法保持轉換後的類型依然是強型別的. 因為我們無法指定TOutput參數在轉換器當中的類型. 在完成轉換之後, 我們可以使用一個迭代來遍曆整個List並訪問Name和IsAdult屬性, 不過我們已經在使用另外一個類型了.

[轉]c#匿名類

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.