# 原則40:使用動態接收匿名類型參數
**By D.S.Qiu**
**尊重他人的勞動,支持原創,轉載請注明出處:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)**
匿名參數的一個缺陷是你不能輕易讓一個方法使用其作為參數或返回值。因為編譯器產生的匿名類型,你不能用來作為方法的參數或返回值。這個問題的任何解決方案都是與局限的。你可以使用匿名類型作為泛型類型參數,或者傳給參數為 System.Object 的方法。這些都不會覺得特別滿意。泛型方法只能假設類型定義在 System.Object 的功能。 System.Object 同樣也有局限。當然,有些時候,你會發現確實需要對一個實際的行為的類命名。本節主要討論當你需要使用不同的匿名類型,即他們有相同命名的屬性但不是你應用的核心部分,而且你不想創建新的命名類型時,你需要做什么。
動態類型可以讓你克服這個限制。動態是在運行時綁定并且使編譯器為可能的運行時類型生成必需的代碼。
假設你需要打印一個價格表的信息。進一步假設你的價格表,可以從多個數據源的產生。你可能有一個存儲倉庫的數據庫,另一個存儲特別訂單,并且還有一個是存儲通過第三方供應商出售的價格。因為它們都是不同的系統,它們可能是不同商品的抽象。這些不同的抽象可能沒有相同名字的屬性,而且它們沒有共同的基類或實現同一個接口。經典的解放方案是實現一個適配器模式(查看 Design Patterns, Gamma, Helm, Johnson, &Vlisside, pp.139-142)為每個產品進行抽象,并轉換每個對象為一致的類型。這有相當多的工作,你要為每個新產品的抽象添加適配器。而且,適配器模式在靜態類型系統會有更好的性能。
另一個,輕量的解決方案是是用動態創建方法,傳入有任何價格信息的類型:
```
public static void WritePricingInformation(dynamic product)
{
Console.WriteLine("The price of one {0} is {1}", product.Name, product.Price);
}
```
你可以創建匿名類型,通過在你的價格方法中匹配屬性,就可以從數據源得到價格信息:
```
var price = from n in Inventory
where n.Cost > 20
select new { n.Name, Price = n.Cost * 1.15M };
```
你任何項目的動態方法創建匿名類型都要包含必需的屬性。只要有命名為“ Price ”和“ Name ”的屬性,WritePricingInformation 方法就可以完成這個工作。
當然,你還可以使用匿名類的其他屬性。只要屬性包含在價格信息中,你就是對的:
```
var orderInfo = from n in Ordered
select new {
n.Name,
Price = n.Cost * 1.15M,
ShippingCost = n.Cost / 10M
};
```
普通的命名的 C# 對象可以在動態中使用。這說明你的價格信息方法可以使用含有正確命名的屬性的具體的類:
```
public class DiscountProduct
{
public static int NumberInInventory { get; set; }
public double Price { get; set; }
public string Name { get; set; }
public string ReasonForDiscount { get; set; }
// other methods elided
}
```
你可能主要到 DiscountProduct 的 Price 屬性的類型是 double 而之前的匿名類型的 Price 的類型是 decimal 。這樣也是可以的。 WritePricingInformation 使用動態靜態類型,所以它會在運行時確定是否正確。當然,如果 DiscountProduct 繼承自 Product 類,并且 Product 類包含 Name 和 Price 屬性,也是可以工作的。
上面的代碼可能會讓你很容易相信我比我實際中更主張使用動態。動態調用的確是解決這個問題的好方法,但不要過度使用。動態調用意味著你需要支付額外的開銷。當你需要時,這個開銷是值得的,但是當你可以避免它,你就應該避免。
你不得不在靜態和動態調用之間做出選擇。你可以創建 WritePricingInformation() 方法的重寫,就可以具體到你對象模型的每個產品類:
```
public class Product
{
public decimal Cost { get; set; }
public string Name { get; set; }
public decimal Price
{
get { return Cost * 1.15M; }
}
}
// Derived Product class:
public class SpecialProduct : Product
{
public string ReasonOnSpecial { get; set; }
// other methods elided
}
// elsewhere
public static void WritePricingInformation(dynamic product)
{
Console.WriteLine("The price of one {0} is {1}", product.Name, product.Price);
}
public static void WritePricingInformation(Product product)
{
Console.WriteLine("In type safe version");
Console.WriteLine("The price of one {0} is {1}",product.Name, product.Price);
}
```
編譯器會針對 Product 或 SpecialProduct 的對象(或者對象模型中任何其他子類的對象)使用具體的版本。對于其他類型,編譯器靜態類型版本當做動態使用。這包括匿名類型。動態綁定器在內部會將每個使用的方法緩存起來。類似調用 WritePricingInformation() 一樣反復調用同一匿名類型的情況,可以減少開銷。一旦方法在第一次調用綁定,就會可以在后續的調用重用。這是零開銷,但動態的實現應該盡可能最小化使用動態的開銷。
你可能懷疑為什么這些方法不能是擴展方法,這樣就可以看起來是匿名類型的成員。的確,那是很不錯的想法,但這在 C# 是不合法的。你不允許創建多態對象的擴展方法。
你可以利用動態創建使用匿名類型的方法。要像刺激的調味品一樣少用這項技術。如果你發現你為了使用匿名類型使用動態調用創建很多方法,這很明顯的跡象,你需要創建具體的類型來表示這些概念。這更容易日后維護,你也會得到類型系統和編譯器的更多支持。然而,當你需要一到兩個使用匿名類型的實用方法,動態調用是創建這個行為的簡單的方法。
小結:
哎,作者一再強調動態耗時,但是一直沒有具體指出哪些過程為什么耗時!
歡迎各種不爽,各種噴,寫這個純屬個人愛好,秉持”分享“之德!
有關本書的其他章節翻譯請[點擊查看](/category/297763),轉載請注明出處,尊重原創!
如果您對D.S.Qiu有任何建議或意見可以在文章后面評論,或者發郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。
轉載請在**文首**注明出處:[http://dsqiu.iteye.com/blog/2090600](/blog/2090600)
更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風)
- 第一章 C# 語言習慣
- 原則1:使用 屬性(Poperty)代替可直接訪問的數據成員(Data Member)
- 原則2:偏愛 readonly 而不是 const
- 原則3:選擇 is 或 as 而不是強制類型轉換
- 原則4:使用條件特性(conditional attribute)代替 #if
- 原則5:總是提供 ToString()
- 原則6:理解幾個不同相等概念的關系
- 原則7:明白 GetHashCode() 的陷阱
- 原則8:優先考慮查詢語法(query syntax)而不是循環結構
- 原則9:在你的 API 中避免轉換操作
- 原則10:使用默認參數減少函數的重載
- 原則11:理解小函數的魅力
- 第二章 .NET 資源管理
- 原則12:選擇變量初始化語法(initializer)而不是賦值語句
- 原則13:使用恰當的方式對靜態成員進行初始化
- 原則14:減少重復的初始化邏輯
- 原則15:使用 using 和 try/finally 清理資源
- 原則16:避免創建不需要的對象
- 原則17:實現標準的 Dispose 模式
- 原則17:實現標準的 Dispose 模式
- 原則18:值類型和引用類型的區別
- 原則19:確保0是值類型的一個有效狀態
- 原則20:更傾向于使用不可變原子值類型
- 第三章 用 C# 表達設計
- 原則21:限制你的類型的可見性
- 原則22:選擇定義并實現接口,而不是基類
- 原則23:理解接口方法和虛函數的區別
- 原則24:使用委托來表達回調
- 原則25:實現通知的事件模式
- 原則26:避免返回類的內部對象的引用
- 原則27:總是使你的類型可序列化
- 原則28:創建大粒度的網絡服務 APIs
- 原則29:讓接口支持協變和逆變
- 第四章 和框架一起工作
- 原則30:選擇重載而不是事件處理器
- 原則31:用 IComparable<T> 和 IComparer<T> 實現排序關系
- 原則32:避免 ICloneable
- 原則33:只有基類更新處理才使用 new 修飾符
- 原則34:避免定義在基類的方法的重寫
- 原則35:理解 PLINQ 并行算法的實現
- 原則36:理解 I/O 受限制(Bound)操作 PLINQ 的使用
- 原則37:構造并行算法的異常考量
- 第五章 雜項討論
- 原則38:理解動態(Dynamic)的利與弊
- 原則39:使用動態對泛型類型參數的運行時類型的利用
- 原則40:使用動態接收匿名類型參數