C#泛型(MSIL)的内部是怎么实现的?

C++中的模板是有编译器进行实例化变成最终可以被直接调用的函数。C#的泛型是MSIL直接支持的,MSIL在实现泛型的时候,就是最终执行的时候也是通过把泛型实例化来运行的吗?谢谢
关注者
131
被浏览
4156
MSIL自身只需声明和使用泛型类型,而无需关心其实例化;.NET是在运行时由CLR来实例化泛型类型的。

跟前面的回答类似,这里用Dictionary<TKey, TVal>举例。

带有未绑定值的泛型参数的泛型类型称为“开放泛型类型”(open generic type)。这可以看作是原始状态、未填值的“模版”。
所有泛型参数都绑定了具体类型的值的泛型类型称为“闭合泛型类型”(closed generic type 或 closed constructed generic type)。
C# / .NET还有“泛型方法”这么一说,本文略过不提,但处理思路跟泛型类型类似。

对Dictionary<TKey, TVal>来说,
  • Dictionary<TKey, TVal> 是其原始状态的open generic type;
  • Dictionary<String, TVal> 是一个constructed type,但尚未填满所有泛型参数,所以虽然是constructed generic type但还不是closed generic type,而还是一种open状态;
  • Dictionary<String, int> 是一个closed generic type。

CLR在运行时会为一个泛型类型的open generic type和所有constructed type(包括closed与尚未closed的)生成各自独立的元数据(例如MethodTable),用于描述该类型的特征;这些元数据也会有一些共享的部分(例如EEClass)。这样所有泛型类型的反射操作就都可以支持了。例如
注意这个实例化是惰性(lazy)的——只有CLR在运行过程中“遇到”的泛型类型才会对其实例化。
这部分跟C++的泛型类型实例化相似。但这里CLR只是为泛型实例化生成了元数据,还没涉及到代码的特化。

using System;

class Program
{
  static void Main()
  {
    var g = typeof(System.Collections.Generic.Dictionary<,>);
    var g1 = g.MakeGenericType(new []{ typeof(string), g.GenericTypeArguments[1] });
    var g2 = g.MakeGenericType(new []{ g1.GenericTypeArguments[0], typeof(int) });
    Console.WriteLine("{0}, IsGenericType: {1}, IsGenericTypeDefinition: {2}, ContainsGenericParameters: {3}", g, g.IsGenericType, g.IsGenericTypeDefinition, g.ContainsGenericParameters);
    Console.WriteLine("{0}, IsGenericType: {1}, IsGenericTypeDefinition: {2}, ContainsGenericParameters: {3}", g1, g1.IsGenericType, g1.IsGenericTypeDefinition, g1.ContainsGenericParameters);
    Console.WriteLine("{0}, IsGenericType: {1}, IsGenericTypeDefinition: {2}, ContainsGenericParameters: {3}", g2, g2.IsGenericType, g2.IsGenericTypeDefinition, g2.ContainsGenericParameters);
  }
}
输出是:
System.Collections.Generic.Dictionary`2[TKey,TValue], IsGenericType: True, IsGenericTypeDefinition: True, ContainsGenericParameters: True                          
System.Collections.Generic.Dictionary`2[System.String,TValue], IsGenericType: True, IsGenericTypeDefinition: False, ContainsGenericParameters: True                
System.Collections.Generic.Dictionary`2[System.String,System.Int32], IsGenericType: True, IsGenericTypeDefinition: False, ContainsGenericParameters: False
上面的例子里g是generic type definition,是最初始的open generic type;
g1是constructed generic type但尚未close;
g2是closed constructed generic type。

只有close generic type才可以创建对象实例或执行方法的代码。CLR在为泛型类型的方法/泛型方法JIT编译出native code时采用了代码共享的设计:
  • 泛型参数绑定的值是值类型时,CLR的JIT编译器会为每一个这样的closed generic type / method生成完全特化的native code,不同实例化泛型类型之间不共享代码,这跟C++的模型一样;
  • 泛型参数绑定的值是引用类型时,CLR的JIT编译器会为所有这样的closed generic type / method生成一份共享的native code,而类型特化的信息存在一个额外的表里面由每个实例化泛型类型自己带着。这样就共享了大部分内容,只带有少量数据不共享。这些不共享的数据主要用来支持诸如new T()、typeof(T)、(T)、is T、as T之类的运算。这比C++的模型稍微复杂一点,优点是共享了更多东西(代码),缺点是在需要对T特化的地方可能会稍微慢一点(但大部分对引用的操作其实都不需要对T特化,所以实际性能并不会受太大影响)。
当然,还得考虑到带有多个泛型参数的泛型类型的情况,但都可以套用上面两条规则去推导。

这么大的功能不可能没有论文,MSR有一篇论文专门讨论CLR的泛型设计与实现:Design and Implementation of Generics for the .NET Common Language Runtime,请仔细阅读参考。