淺談.NET中泛型的基本原理


  本片繼承前面幾篇一貫的特點,淺談胡侃。

  1 .NET為什么要引入泛型?

  說到.NET泛型,應該都不陌生,畢竟泛型是.NET 2.0中就推出的特性,各位博友應該都知道引入泛型的最主要目的是為了解決裝箱、拆箱帶來的性能損失,說的當然沒有錯,但是不夠“太具體”,確切來講泛型解決了原先無法避免的容器操作的裝箱拆箱問題

  目的就說這么多吧,言簡意賅,該說的說了,多說無益。

  2.淺談.NET泛型原理

  有過C++編程經驗的博友對C++中的模板,一定不陌生,泛型的語法和概念和C++中的模板極其類似,在C++中模板的目的是為了設計更加通用的類型,在.NET中也是這樣,當然還有另外一個重要的作用,就是前面所說的:避免容器操作中的裝箱和拆箱操作!

  先寫一個一段簡單的代碼來示例下,代碼如下:

using System;

namespace 淺談泛型的示例
{
    /// <summary>
    /// DebugLZQ
    /// http://www.cnblogs.com/DebugLZQ
    /// 定義一個簡單泛型類
    /// </summary>
    public class SimpleGenericClass<T>
    {
        T my;
        public SimpleGenericClass(T t)
        {
            my = t;
        }
        public override string ToString()
        {
            return my.ToString();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            SimpleGenericClass<string> genericClass = new SimpleGenericClass<string>("DebugLZQ");
            Console.WriteLine(genericClass);

            Console.ReadKey();
        }
    }
}

  程序運行結果如下:

  首先,程序申明了一個泛型(類型)SimpleGenericClass<T>。注意編碼規范:和所有的接口名稱都以I開頭一樣所有的泛型參數都以T開頭。

  不要被泛型表面的復雜迷惑,和.NET其他類型一樣,帶泛型參數的類型同樣是一個確定的類型,在不被指定的情況下,它直接繼承自System.Object類型,並且可以派生出其他類型。
  但是泛型類型和普通的類型還是有一定的區別的。通常泛型類型被稱為“開放式類型”,.NET機制規定開放式類型不能被實例化,這樣就確保了泛型(開放式類型)在參數類型被指定之前,不能被實例化成任何對象。 實際上,.NET也沒有辦法進行實例化,因為不能確定需要分配多少內存給開放式類型。 

  然后在Main方法中,指定了開放類型(泛型)的參數,這個時候重新定義了一個新的封閉類型SimpleGenericClass<string> ,針對該類型的所有實例化都是合法的。注意,雖然為開放類型提供泛型的參數導致了一個新的封閉類型的生成,但這不代表新的封閉類型和開放類型由任何派生繼承的關系,事實上,兩者在類結構上處於同一個層次,並且兩者沒有任何關系。

  最后說明下:在System.Collections.Generic名稱空間下定義了一些諸如List<>、Dictionary<,>等泛型容器,並且在System.Array中定義了一些靜態的泛型方法,MS鼓勵使用新的泛型容器來代替.NET版中的容器和方法,以提高程序的性能。

  3.CLR層面上的泛型

  下面是lz回復博友清風的,內容大概是lz從CLR層面上理解的泛型。大家批評指正下:

  @清風揚諰
  你給出了lz一個不同的理解切入點,清風你這個是要把泛型的CLR本質拉出來啊,關於.NET泛型的原理lz暫切寫到這,同時lz建議清風兄整理下,lz拜讀~這樣叫lz來講的話,估計我只能說個大概而且不一定正確,lz肯定需要去翻clr via c#之類的參考書了~
  如果一定要從CLR層面來講的話,lz的理解大概是這樣的:泛型是基於JIT的,由CLR運行時支持的。代碼經過第一次C#編譯器編譯后,產生的IL是並沒有為T指定一個特定的類型,這個lz在文中有說過泛型這種開放類型,不能被實例化,當然這時候T還是T。真正的泛型實例化,發生在JIT編譯時,產生的機器碼取決於給T傳入的類型。
  對於值類型,JIT把IL中的參數T,替換為傳入的值類型,並且編譯為本機代碼。由於值類型的操作,就是操作數據本身。JIT不會為不知道大小的參數生成相同的本機代碼,因此在以后的場合中,JIT都會去尋找是否存在了相同的代碼,如果是就不會重新編譯,而是引用現有的代碼(注意如果傳入的是不同的值類型參數,當然生成不同的代碼!)。因為是引用也就沒有了C++中的代碼內聯造成的“代碼爆炸”問題。
對於引用類型,JIT把T替換為Object,由於引用類型變量就是一個指向(托管)堆的某個地址的指針,對於不同指針的操作完全可以采用相同的方式,因為都是指針。因此引用類型共用一個代碼。
  總之:參數是值類型的是不同的類型生成不同的代碼,生成某個類型代碼后,使用引用;引用類型是共用一個代碼,指針操作嘛!同時lz前面說到:泛型,是基於JIT的,也就意味着無論是值類型還是引用類型都將共用一個占位符,引用的是同一個類型下的方法和方法槽表。
  就這么大概得回復下清風吧,覺得大家交流起來還是很有意思的嘛~

  4.一點補充

  下面是博友 kennywangjin的一些補充:

作一點補充,獻丑:
  事實上,泛型對性能的提升僅僅對值類型有效,對引用類型幾乎可以忽略不計,因為引用類型不需要裝箱拆箱(不嚴謹)。
引用類型的泛型可以共用代碼,而對於值類型來說,JIT會為每種值類型生成各自的封閉類型,並不會節省代碼量,事實上,還會加重JIT編譯的負擔——盡管幾乎可以忽略不計。
  個人認為,泛型真正的優勢在於編譯時類型安全,結合IDE的智能提示,極大地提高了編碼效率,至於性能,試想下,我們有多少情況會用到純粹值類型的泛型呢?大部分情況下都還是引用類型的泛型。
  甚至在某些極端情況下,泛型有略微降低性能的情況——對於引用類型來說。但這些依然不能妨礙其成為.NET平台最有意義的一次升級之一。

 請點擊下面的綠色通道---關注DebugLZQ,共同交流進步~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM