2. 解析泛型
还是先看看一段代码:
public void intrGeneric<T>()
{
Console.WriteLine(typeof(T));
}
intrGeneric<int>();
intrGeneric<object>();
intrGeneric<string>();
{
Console.WriteLine(typeof(T));
}
intrGeneric<int>();
intrGeneric<object>();
intrGeneric<string>();
那么当我们不知道intrGeneric<T>的T的类型的时候,IL是在怎么处理的呢。看看产生的IL:
.method public hidebysig instance void intrGeneric<T>() cil managed noinlining
{
// Code size 18 (0x12)
.maxstack 8
IL_0000: nop
IL_0001: ldtoken !!T
IL_0006: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_000b: call void [mscorlib]System.Console::WriteLine(object)
IL_0010: nop
IL_0011: ret
} // end of method Generics_CSharp::intrGeneric
{
// Code size 18 (0x12)
.maxstack 8
IL_0000: nop
IL_0001: ldtoken !!T
IL_0006: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_000b: call void [mscorlib]System.Console::WriteLine(object)
IL_0010: nop
IL_0011: ret
} // end of method Generics_CSharp::intrGeneric
在这里我们可以看到编译器产生的IL是并没有为T指定一个特定的类型,但是输出的结果为
System.Int32,System.Object,System.String。我们知道typeof()输出的Runtime Type。我们很容易的得出泛型的另外一个特性:运行时本质。由CLR运行时支持,真正的泛型实例化是发生在JIT编译时,生成不同的本地代码。我们再来看看上面的IL代码:ldtoken !!T,ldtoken指令:将元数据标记转换为其运行时表示形式,并将其推送到计算堆栈上。!!T时编译器生成的一个占位符,工作形式时这样的:第一次编译的时候,首先生成IL代码以及元数据,T只是一个隐藏的符号,这是并没有对泛型类型进行实例化,当JIT编译的时候,将以实际类型替换IL代码和元数据的T符号,并将其转换为本地代码。
说到这里我们大概对泛型有个大概的了解。Java编译器在编译泛型的时候,会将所有的泛型参数替换为Object,这实际上还是存在装箱拆箱的过程。还有C++的实现也存在一个很大的问题,那就是代码爆炸,C++会为每种类型都要生成自己的一份代码。C#与JAVA比较解决了性能问题,与C++比较,解决了代码爆炸的问题。那么C#是怎么解决代码爆炸的问题,做到代码共享的呢?这也是C#泛型的一个特性。