通用方法如何、何时、何地具体化
本文关键字:何时 具体化 何地 方法 | 更新日期: 2023-09-27 18:35:50
这个问题让我想知道泛型方法的具体实现实际上是在哪里存在的。我已经尝试过谷歌,但没有想出正确的搜索。
如果我们举这个简单的例子:
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,然后是一个双精度,然后是一个字符串并返回给调用者呢?这是 JIT 过程的操作吗?我是否以完全错误的方式看待这个问题?
在 C# 中,运行时本身支持泛型类型和方法的概念。 C# 编译器不需要实际创建泛型方法的具体版本。
实际的"具体"泛型方法是由 JIT 在运行时创建的,在 IL 中不存在。 首次将泛型方法与类型一起使用时,JIT 将查看是否已创建泛型方法,如果没有,则为该泛型类型构造适当的方法。
这是泛型和C++中的模板等东西之间的根本区别之一。 这也是泛型存在许多限制的主要原因 - 由于编译器实际上并没有为类型创建运行时实现,因此接口限制由编译时间约束处理,这使得泛型在潜在用例方面比C++模板更具限制性。 但是,运行时本身支持它们的事实允许以C++和其他编译时创建的模板实现不支持的方式从库中创建泛型类型和用法。
与往常一样,泛型方法的实际机器代码是在方法抖动时创建的。 此时,抖动首先检查之前是否抖动过合适的候选项。 这是非常常见的情况,其具体运行时类型 T 为引用类型的方法的代码只需要生成一次,并且适用于每个可能的引用类型 T。 T 上的约束确保此机器代码始终有效,事先由 C# 编译器检查。
可以为值类型的 T 生成其他副本,它们的机器代码不同,因为 T 值不再是简单的指针。
所以是的,在您的情况下,您最终会得到三种不同的方法。 <string>
版本可用于任何引用类型,但您没有其他引用类型。 <int>
和<double>
版本符合"T's that is value types"类别。
否则,一个很好的例子是,这些方法的返回值以不同的方式传递回调用方。 在 x64 抖动上,字符串版本返回带有 RAX 寄存器的值,就像任何返回的指针值一样,int 版本返回 EAX 寄存器,双精度版本返回 XMM0 寄存器。