2013-10-16 115 views
72

This question让我想知道一个通​​用方法的具体实现在哪里实际存在。我试过谷歌,但没有提出正确的搜索。泛型方法如何,何时何地使混凝土变为混凝土?

如果我们把这个简单的例子:

class Program 
{ 
    public static T GetDefault<T>() 
    { 
     return default(T); 
    } 

    static void Main(string[] args) 
    { 
     int i = GetDefault<int>(); 
     double d = GetDefault<double>(); 
     string s = GetDefault<string>(); 
    } 
} 

在我的脑子里,我一直认为,在某些时候它导致执行与3个必需的具体实现,例如,在天真的伪忙玲,我们本来这个逻辑具体implementaiton在特定类型的使用导致了正确的堆栈分配等

class Program 
{ 
    static void Main(string[] args) 
    { 
     int i = GetDefaultSystemInt32(); 
     double d = GetDefaultSystemFloat64(); 
     string s = GetDefaultSystemString(); 
    } 

    static int GetDefaultSystemInt32() 
    { 
     int i = 0; 
     return i; 
    } 
    static double GetDefaultSystemFloat64() 
    { 
     double d = 0.0; 
     return d; 
    } 
    static string GetDefaultSystemString() 
    { 
     string s = null; 
     return s; 
    } 
} 

望着IL用于一般程序它仍然是在泛型类型来表示:

.method public hidebysig static !!T GetDefault<T>() cil managed 
{ 
    // Code size  15 (0xf) 
    .maxstack 1 
    .locals init ([0] !!T CS$1$0000, 
      [1] !!T CS$0$0001) 
    IL_0000: nop 
    IL_0001: ldloca.s CS$0$0001 
    IL_0003: initobj !!T 
    IL_0009: ldloc.1 
    IL_000a: stloc.0 
    IL_000b: br.s  IL_000d 
    IL_000d: ldloc.0 
    IL_000e: ret 
} // end of method Program::GetDefault 

那么如何以及在什么时候决定一个int,然后是一个double,然后一个字符串必须在堆栈上分配并返回给调用者?这是JIT过程的一个操作吗?我是否完全错误地看待这个问题?

+5

和我一样,你似乎正在用C++术语思考这个问题。我不知道答案,但回想一下在C#中关于泛型的一些意想不到的事实。 –

+4

你看起来不错,IL支持泛型。伟大的进步是已编译的程序集中的类仍然支持泛型。 _(像整个.NET框架)_ –

+0

@Jonathan Wood完全从C++如何修改方法名称的角度来看待这个问题! – dkackman

回答

77

在C#中,运行时本身支持泛型类型和方法的概念。 C#编译器不需要实际创建泛型方法的具体版本。

实际的“具体”泛型方法是由JIT在运行时创建的,并且不存在于IL中。第一次将泛型方法与类型一起使用时,JIT将查看它是否已创建,如果不是,则为该泛型类型构造适当的方法。

这是仿制药和C++模板之类的基本差异之一。这也是泛型的许多限制的主要原因 - 因为编译器实际上并没有为类型创建运行时实现,所以接口限制是由编译时间约束来处理的,这使得泛型比C++中的模板更受限制潜在用例。但是,它们在运行时本身支持的事实允许以C++和其他编译时创建的模板实现中不支持的方式创建通用类型和库的用法。

+1

事实上,C++模板和C#泛型之间的质的区别在于,如果不是时间和内存的限制,相对较小的可执行文件可能会生成无限数量的可识别的不同类型的实例(例如,程序可能取一个任意长度的输入字符串(例如“FRED”)并创建一个类型为'F >>>'的实例。 – supercat

+2

好回答里德。任何描述你知道的参考资料? – dkackman

+7

@dkackman我想通读http://www.artima.com/intv/generics.html在这里讨论它 –

45

通用方法的实际机器代码与创建方法一样,一直创建。此时,抖动首先检查是否有合适的候选人在之前发生过爆炸。通常情况下,其具体运行时类型T是引用类型的方法的代码只需要生成一次,并且适用于每个可能的引用类型T. T上的约束确保此机器代码始终有效,以前由C#编译器检查过。

可能为T的值类型生成额外的副本,它们的机器代码是不同的,因为T值不再是简单的指针。

所以是的,在你的情况下,你最终会得到三种不同的方法。 <string>版本可用于任何参考类型,但您没有其他类型。 <int><double>版本符合“T值类型”类别。

否则就是一个很好的例子,这些方法的返回值以不同的方式返回给调用者。在x64抖动中,字符串版本与RAX寄存器一起返回值,就像任何返回的指针值一样,int版本返回EAX寄存器,双倍版本返回XMM0寄存器。