# 原则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:使用动态接收匿名类型参数