泛型不是模板

译自Eric Lippert's Blog, 原文: http://blogs.msdn.com/ericlippert/archive/2009/07/30/generics-are-not-templates.aspx

因为我不是常人, 所以我喜欢去了解容易混淆的东西间的微妙差别:

  • 我的脑袋里还是非常地不明白集线器路由器交换机之间的区别,并且也不明白他们怎么在里面联系起来的。
  • 找到的大块的矿石事实上是岩石;只要你把它们用于花园或建造一座桥梁, 它们就突然变成了石料
  • 当一只达到了120磅,它就是一只肥猪

我想我可以为编程语言设计中一些容易困惑的概念做一个小系列.

这里是一个我经常遇到的问题:

Code

当你调用C.DoIt<string>会发生什么呢?很多人希望打印出“string”, 然而不管T是什么,事实上总是打印出“everything else”。

C#语言标准说当你需要面临选择调用ReallyDoIt<string>(string)或是ReallyDoIt(string)的时候,也就是说在需要从两个相同签名方法选择, 其中包含一个是泛型方法的时候,我们会选择非泛型的方法而不是泛型的方法。但在这里我们为什么不这么做呢?

因为那不是标准所说的选择。如果你说要调用ReallyDoIt("hello world");

那么我们会选择非泛型的版本。但是你并不是把编译器所知道的string类型进行参数传递。你只是传递了类型T,一个没有约束的类型参数,所以它可以是任何类型。所以由于重载决议的算法,有没有一个方法能接收任何类型作为参数呢?回答是有。

这个C#里泛型的例子并不像C++里面的模板。你可以把模板想象成一个奢华的搜索替代的机制。当你说你要在模板中调用DoIt<string>, 编译器会概念性地搜索出所有用到的“T”,再把它们替换成“string” ,接着再编译源代码。重载决议用替代的已知类型参数来执行,生成的代码也反映了重载决的结果。

泛型可不是这么工作的。泛型是这样的,好吧,一般的。我们只做一次重载决议然后固定了结果。当任何程序从一个完全不同的程序集用string作为类型参数调用到这个方法的时候,我们不会在运行时改变它。我们为泛型生成的IL代码已经定好了将要去选出调用的方法。那个JIT并不是说“好吧,如果我们让C#编译器用这个额外信息去马上执行我碰巧就知道了,那么它就会挑一个不同的重载. 让我重写一下生成的(IL)代码吧,让编译器忽略以前生成的”。 JIT是不知道任何C#的条条框框的。

本质上来说,上面的例子和这个没有区别:

Code

当编译器为调用ReallyDoIt生成(IL)的时候, 它会挑选object作为参数的版本,因为那是它最好的选择。如果有人用string来调用方法,那么它还是会选择object作为参数的版本。

现在,如果你确实想根据参数的类型在运行时做重载决议,我们能为你做到。那就是C# 4.0的新特性dynamic能做的。只要把object替换成dynamic, 那么当你调用那个object的方法时, 我们会在运行时根据它所知道的所有的运行时类型,执行重载决议并且动态地生成编译器选择的调用代码。

posted @ 2009-10-16 14:40  李占卫  阅读(780)  评论(0编辑  收藏  举报