歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 深入理解C#泛型

深入理解C#泛型

日期:2017/3/1 9:33:10   编辑:Linux編程

前面兩篇文章介紹了C#泛型的基本知識和特性,下面我們看看泛型是怎麼工作的,了解一下泛型內部機制。

  1. http://www.linuxidc.com/Linux/2015-02/113465.htm
  2. http://www.linuxidc.com/Linux/2015-02/113466.htm

泛型內部機制

泛型擁有類型參數,通過類型參數可以提供"參數化"的類型,事實上,泛型類型的"類型參數"變成了泛型類型的元數據,"運行時"在需要的時候會利用他們構造恰當的類型,通過這些類型,我們有可以實例化不同類型的對象。也就是說,未綁定泛型類型是以構造泛型類型的藍圖,已構造泛型類型又是實際對象的藍圖。

分析泛型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.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運行時自動調用,而不能人工調用,並且只能執行一次。

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

總結

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

C#多線程編程實例 線程與窗體交互【附源碼】 http://www.linuxidc.com/Linux/2014-07/104294.htm

C#數學運算表達式解釋器 http://www.linuxidc.com/Linux/2014-07/104289.htm

在C語言中解析JSON配置文件 http://www.linuxidc.com/Linux/2014-05/101822.htm

C++ Primer Plus 第6版 中文版 清晰有書簽PDF+源代碼 http://www.linuxidc.com/Linux/2014-05/101227.htm

Copyright © Linux教程網 All Rights Reserved