72

这个问题让我想知道泛型方法的具体实现实际上是在哪里存在的。我已经尝试了谷歌,但没有提出正确的搜索。

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

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流程的操作吗?我是在完全错误地看待这个问题吗?

4

2 回答 2

78

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

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

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

于 2013-10-16T18:14:42.223 回答
45

像往常一样,当方法被 jitted 时,会创建泛型方法的实际机器代码。此时,jitter 首先检查之前是否有合适的候选人被 jitter。这是很常见的情况,具体运行时类型 T 是引用类型的方法的代码只需要生成一次,并且适用于每个可能的引用类型 T。对 T 的约束确保该机器代码始终有效,之前由 C# 编译器检查过。

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

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

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

于 2013-10-16T18:16:07.293 回答