通用方法如何,何时何地具体化?


72

这个问题让我想知道通用方法的具体实现在哪里真正存在。我已经尝试过Google,但没有提出正确的搜索条件。

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

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种必要的具体实现,以便在天真伪整型中,我们将拥有这种逻辑的具体实现,其中所使用的特定类型会导致正确的堆栈分配等。 。

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框架一样)
Jeroen van Langen 2013年

@Jonathan Wood完全从C ++如何处理方法名称的角度看待这个问题!
dkackman 2013年

2
请注意,尽管当前的CLR在运行时对具体类型参数进行了一些专门化,但对于所有可能的类型,通过JIT编译的一种方法可能会有所不同。泛型的概念根本不决定运行时的实现。
usr

Answers:


78

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

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

这是泛型和C ++中的模板之类的东西之间的根本区别之一。这也是泛型存在许多限制的主要原因-由于编译器实际上并未为类型创建运行时实现,因此接口限制由编译时间约束来处理,这使得泛型比C ++中的模板更具限制性潜在的用例。但是,它们在运行时本身受支持的事实允许以C ++和其他编译时创建的模板实现所不支持的方式从库中创建泛型类型和用法。


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

2
好的答案里德。您是否知道任何描述此内容的参考资料?
dkackman

7
@dkackman我已经阅读了artima.com/intv/generics.html
Reed Copsey 2013年

4
为了补充Reed的最后一段,一个这样的情况是使用一个泛型(也许来自库),该泛型具有在泛型编译之后创建的类型。要使用c ++做到这一点,编译器将需要模板代码的源。
2013年

45

与常规方法一样,当通用方法被绑定时,它会创建实际的机器代码。在这一点上,抖动首先检查是否有合适的候选人之前被抖动过。这是非常常见的情况,具体运行时类型T是引用类型的方法的代码只需生成一次即可,并且适用于每种可能的引用类型T。对T的约束确保该机器代码始终有效,先前由C#编译器检查过。

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

所以是的,在您的情况下,您将得到三种不同的方法。该<string>版本可用于任何引用类型,但您没有其他引用类型。而<int><double>版本符合“T的是值类型”的范畴。

否则,将是一个很好的示例,这些方法的返回值将以不同的方式传递回调用方。在x64抖动上,字符串版本通过RAX寄存器返回值,就像返回的任何指针值一样,整数版本通过EAX寄存器返回,双精度版本通过XMM0寄存器返回。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.