標籤:

C#泛型(MSIL)的內部是怎麼實現的?

C++中的模板是有編譯器進行實例化變成最終可以被直接調用的函數。C#的泛型是MSIL直接支持的,MSIL在實現泛型的時候,就是最終執行的時候也是通過把泛型實例化來運行的嗎?

謝謝


MSIL自身只需聲明和使用泛型類型,而無需關心其實例化;.NET是在運行時由CLR來實例化泛型類型的。

跟前面的回答類似,這裡用Dictionary&舉例。

帶有未綁定值的泛型參數的泛型類型稱為「開放泛型類型」(open generic type)。這可以看作是原始狀態、未填值的「模版」。

所有泛型參數都綁定了具體類型的值的泛型類型稱為「閉合泛型類型」(closed generic type 或 closed constructed generic type)。

C# / .NET還有「泛型方法」這麼一說,本文略過不提,但處理思路跟泛型類型類似。

對Dictionary&來說,

  • Dictionary& 是其原始狀態的open generic type;

  • Dictionary& 是一個constructed type,但尚未填滿所有泛型參數,所以雖然是constructed generic type但還不是closed generic type,而還是一種open狀態;
  • Dictionary& 是一個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,請仔細閱讀參考。


對於類型參數是值類型的(int,date,等等)的泛型 .net在編譯時候會單獨為這個泛型生成一個類,拿list&舉例 其實是生成了List& List&這些類,對於類型參數是引用類型的(string等等)的泛型 .net在編譯時候會為所以這些泛型生成一個類 類裡面的泛型引用全部用一個引用代替(object)其實所有List& List& 都指的是List&


推薦閱讀:

.NET/CLR都開源了,VC++還遠嗎?
所有CLR開發人員都應該了解的關於運行時異常的知識(上)
如何開始學習CoreCLR源代碼?
C#里的顯式介面實現是什麼原理?
程序集什麼玩意?我知道其表現形式為dll和exe,但是exe不是直接執行的文件嗎?而dll只是類庫,供exe調用代碼?

TAG:NET | C# | CLR |