C#泛型內部工作機制詳細解析
前面兩篇文章介紹了C#泛型的基本知識和特性,下面我們看看泛型是怎麼工作的,瞭解一下泛型內部機制。
泛型內部機制
泛型擁有型別引數,通過型別引數可以提供”引數化”的型別,事實上,泛型型別的”型別引數”變成了泛型型別的後設資料,”執行時”在需要的時候會利用他們構造恰當的型別,通過這些型別,我們有可以例項化不同型別的物件。也就是說,未繫結泛型型別是以構造泛型型別的藍圖,已構造泛型型別又是實際物件的藍圖。
分析泛型IL程式碼
下面看一個例子,在這個例子中定義了一個用於比較的泛型類和一個比較int的非泛型類:
namespace GenericTest { class CompareUtil<T> where T: IComparable { public T ItemOne { get; set; } public T ItemTwo { get; set; } public CompareUtil(T itemOne, T itemTwo) { this.ItemOne = itemOne; this.ItemTwo = itemTwo; } public T GetBiggerOne() { if (ItemOne.CompareTo(ItemTwo) > 0) { return ItemOne; } return ItemTwo; } } class IntCompareUtil { public int ItemOne { get; set; } public int ItemTwo { get; set; } public IntCompareUtil(int itemOne, int itemTwo) { this.ItemOne = itemOne; this.ItemTwo = itemTwo; } public int GetBiggerOne() { if (ItemOne.CompareTo(ItemTwo) > 0) { return ItemOne; } return ItemTwo; } } class Program { static void Main(string[] args) { CompareUtil<int> compareInt = new CompareUtil<int>(3, 6); int bigInt = compareInt.GetBiggerOne(); IntCompareUtil intCompareUtil = new IntCompareUtil(4, 7); int big = intCompareUtil.GetBiggerOne(); Console.Read(); } } }
首先,通過ILSpy檢視一下泛型類”CompareUtil<T>”的IL程式碼(只列出了一部分IL程式碼)
.class private auto ansi beforefieldinit GenericTest.CompareUtil`1<([mscorlib]System.IComparable) T> extends [mscorlib]System.Object { …… .method public hidebysig specialname rtspecialname instance void .ctor ( !T itemOne, !T itemTwo ) cil managed {……} …… // Properties .property instance !T ItemOne() { .get instance !0 GenericTest.CompareUtil`1::get_ItemOne() .set instance void GenericTest.CompareUtil`1::set_ItemOne(!0) } .property instance !T ItemTwo() { .get instance !0 GenericTest.CompareUtil`1::get_ItemTwo() .set instance void GenericTest.CompareUtil`1::set_ItemTwo(!0) } } // end of class GenericTest.CompareUtil`1
大家可以檢視非泛型類”IntCompareUtil”的IL程式碼,你會發現泛型類的IL程式碼跟非泛型類的IL程式碼基本一致,只是泛型類的IL程式碼中多了一些型別引數後設資料。
下面看看泛型類IL程式碼中的幾個特殊點:
-
GenericTest.CompareUtil`1<([mscorlib]System.IComparable) T>
- `1表示元數,也就是型別引數的數量
- <([mscorlib]System.IComparable) T>就是我們加在泛型型別上的型別約束
-
!T和!0
- !T就是型別引數的佔位符
- !0代表第一個型別引數(當泛型的元數為2時,!1就代表第二個型別引數)
同時,大家也可以比較一下泛型類和非泛型類的例項構造IL程式碼
IL_0003: newobj instance void class GenericTest.CompareUtil`1<int32>::.ctor(!0, !0) IL_0012: newobj instance void GenericTest.IntCompareUtil::.ctor(int32, int32)
泛型機制
根據上面的分析可以得到, C#泛型能力有CLR在執行時支援,編譯器在處理泛型的時候做了兩件事情:
- 當編譯器遇到”CompareUtil<T>”這種泛型程式碼時,編譯器會把泛型程式碼編譯為IL程式碼和後設資料時,採用特殊的佔位符來表示型別引數
- 而真正的泛型例項化工作以”on-demand”的方式,也就是說當編譯器遇到”CompareUtil<int> compareInt”指定型別實參的程式碼時,根據型別實參JIT將泛型型別的IL轉換成本機程式碼,這個原生程式碼中已經使用了實際的資料型別,等同於用實際型別宣告的類
值型別和引用型別的例項化
JIT為所有型別引數為”引用型別”的泛型型別產生同一份本機程式碼,之所以能這麼做,是由於所有的引用具有相同的大小。
但是如果型別引數為”值型別”,對每一個不同的”值型別”,JIT將為其產生一份獨立的本機程式碼。
至於說為什麼使用泛型類可以避免值型別的裝箱和拆箱操作:
List<int> intList = new List<int>();
相信大家看到下面的IL程式碼就明白了,在泛型類中,都是通過型別引數直接使用值型別。
// Fields .field private !T[] _items
對泛型型別使用typeof
在C#中,我們經常使用typeof操作符來獲得一個System.Type物件的引用。
對於泛型型別,我們也可以通過兩種方式使用typeof:
-
獲取泛型型別定義(未繫結泛型型別)
- 為了獲取泛型型別的定義,只需要提供宣告的型別名稱,刪除所有的型別引數,但保留逗號
-
獲取特定的已構造型別(也就是獲取封閉型別的型別引用)
- 只需要指定型別實參
下面看一個簡單的例子,
static void DemonstrateTypeOf<T>() { Console.WriteLine(typeof(T)); Console.WriteLine(typeof(List<>)); Console.WriteLine(typeof(Dictionary<,>)); Console.WriteLine(typeof(List<T>)); Console.WriteLine(typeof(Dictionary<string, T>)); Console.WriteLine(typeof(List<long>)); Console.WriteLine(typeof(Dictionary<string, int>)); }
函式的輸出如下:
System.Double System.Collections.Generic.List`1[T] System.Collections.Generic.Dictionary`2[TKey,TValue] System.Collections.Generic.List`1[System.Double] System.Collections.Generic.Dictionary`2[System.String,System.Double] System.Collections.Generic.List`1[System.Int64] System.Collections.Generic.Dictionary`2[System.String,System.Int32]
通過輸出的結果,我們也可以看到每個泛型的元數,以及泛型型別(未繫結泛型型別和封閉型別)的型別。
靜態欄位和靜態建構函式
泛型中的靜態欄位
在C#中,類的靜態成員變數在不同的類例項間是共享的,並且可以通過類名訪問。C# 2.0中引入了泛型,導致靜態成員變數的機制出現了一些變化:靜態成員變數在相同封閉型別間共享,不同的封閉型別間不共享。這也非常容易理解,因為不同的封閉型別雖然有相同的類名稱,但由於分別傳入了不同的資料型別,他們是完全不同的型別。
看一個簡單的例子:
namespace GenericTest { class TypeWithField<T> { public static string field; public static void PrintField() { Console.WriteLine(field); } } class Program { static void Main(string[] args) { TypeWithField<int>.field = "Int Field"; TypeWithField<string>.field = "String Field"; TypeWithField<int>.PrintField(); TypeWithField<string>.PrintField(); Console.Read(); } } }
泛型中的靜態建構函式
靜態建構函式的規則:只能有一個,且不能有引數,他只能被.NET執行時自動呼叫,而不能人工呼叫,並且只能執行一次。
泛型中的靜態建構函式的原理和非泛型類是一樣的,只需把泛型中的不同的封閉類理解為不同的類即可。
總結
本篇文章介紹了泛型的工作機制,進一步的認識了泛型。同時,結合泛型工作原理,看到了為什麼值型別使用泛型可以避免裝箱和拆箱。
相關文章
- 泛型,內部類泛型
- Java中泛型的詳細解析,深入分析泛型的使用方式Java泛型
- 匿名內部類泛型泛型
- 詳解C#泛型(三)C#泛型
- 詳解C#泛型(二)C#泛型
- 詳解C#泛型(一)C#泛型
- C#之泛型詳解C#泛型
- C# 之泛型詳解C#泛型
- java泛型詳細介紹Java泛型
- C# 泛型C#泛型
- C#泛型C#泛型
- 【C#】-泛型C#泛型
- MySQL索引機制(詳細+原理+解析)MySql索引
- C# 泛型方法C#泛型
- hashtable 泛型 C#泛型C#
- c#泛型集合C#泛型
- 詳解 Rainbond Ingress 泛解析域名機制AI
- Java核心知識1:泛型機制詳解Java泛型
- ElasticSearch 文件(document)內部機制詳解Elasticsearch
- Library cache內部機制詳解
- Spark Shuffle機制詳細原始碼解析Spark原始碼
- .NET泛型解析(上)泛型
- .NET泛型解析(下)泛型
- 淺談C#泛型C#泛型
- C#泛型約束C#泛型
- C#泛型學習C#泛型
- C# 泛型集合SortedListC#泛型
- c#泛型的使用C#泛型
- c#泛型續(一)C#泛型
- 詳解Java Socket的工作機制Java
- Oracle Library cache內部機制詳解Oracle
- Java的泛型機制Java泛型
- C#語言入門詳解(劉鐵錳)---泛型C#泛型
- Java 泛型詳解Java泛型
- Java泛型詳解Java泛型
- c#進階之泛型C#泛型
- C#中的泛型-1C#泛型
- C#泛型類之LISTC#泛型