深入理解C#泛型

來源:互聯網
上載者:User

標籤:

前面兩篇文章介紹了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在運行時支援,編譯器在處理泛型的時候做了兩件事情:

  1. 當編譯器遇到"CompareUtil<T>"這種泛型代碼時,編譯器會把泛型代碼編譯為IL代碼和中繼資料時,採用特殊的預留位置來表示型別參數
  2. 而真正的泛型執行個體化工作以"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.DoubleSystem.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運行時自動調用,而不能人工調用,並且只能執行一次。

泛型中的靜態建構函式的原理和非泛型類是一樣的,只需把泛型中的不同的封閉類理解為不同的類即可。

總結

本篇文章介紹了泛型的工作機制,進一步的認識了泛型。同時,結合泛型工作原理,看到了為什麼實值型別使用泛型可以避免裝箱和拆箱。

 

深入理解C#泛型

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.