原则40:使用动态接收匿名类型参数

By D.S.Qiu

尊重他人的劳动,支持原创,转载请注明出处: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# 是不合法的。你不允许创建多态对象的扩展方法。

你可以利用动态创建使用匿名类型的方法。要像刺激的调味品一样少用这项技术。如果你发现你为了使用匿名类型使用动态调用创建很多方法,这很明显的迹象,你需要创建具体的类型来表示这些概念。这更容易日后维护,你也会得到类型系统和编译器的更多支持。然而,当你需要一到两个使用匿名类型的实用方法,动态调用是创建这个行为的简单的方法。

小结:

哎,作者一再强调动态耗时,但是一直没有具体指出哪些过程为什么耗时!

欢迎各种不爽,各种喷,写这个纯属个人爱好,秉持”分享“之德!

有关本书的其他章节翻译请点击查看,转载请注明出处,尊重原创!

如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件([email protected])交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。

转载请在文首注明出处:http://dsqiu.iteye.com/blog/2090600

更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)