.net泛型理解


泛型簡介:

  泛型(Generic Type)是.NET Framework2.0最強大的功能之一。泛型的主要思想是將算法與數據結構完全分離開,使得一次定義的算法能作用於多種數據結構,從而實現高度可重用的開發。通過泛型可以定義類型安全的數據結構,而沒有必要使用實際的數據類型,這將顯著提高系統性能並得到高質量的代碼(因為可以重用數據處理算法,沒有必要復制類型特定的代碼)。

泛型工作原理:

  通過泛型可以定義類型安全並且對性能或工作效率無損害的類。表面上,C#泛型的語法和C++模板類似,但編譯器在實現和支持他們的方式存在重要的差異。與C++模板相比,C#泛型可以提供增強的安全性,但在功能方面也受到某種程度的限制。在一些C++編譯器中,在通過特定類型使用模板類之前,編譯器甚至不會編譯模板代碼,當確實指定了類型時,編譯器會以內聯方式插入代碼,並且將每個出現一般類型參數的地方替換為指定的類型。此外,每當使用特定類型時,編譯器都會插入特定於該類型的代碼,而不管是否已經在應用程序的其他位置為模板類指定了該類型,其中C++鏈接器負者解決該問題,但並不總是有效,而這也可能會導致代碼膨脹,從而增加加載時間和內存足跡。

  在.net Framewrok 2.0 中,泛型在IL和CLR本身中具有本機支持。在編譯泛型c#代碼時,像其他任何類型一樣,首先編譯器會將其編譯為IL,但是,IL只包含實際特定類型的參數或占位符,並有專用的IL指令支持泛型操作。泛型代碼的元數據中包含泛型信息。真正的泛型實例化工作是以“on-demand”的方式,發生在JIT編譯時。當進行JIT編譯時,JIT編譯器用指定的類型實參來替換泛型IL代碼元數據中的T,進行泛型類型的實例化。這會像JIT編譯器提供類型特定的IL元數據定義,就好像從未涉及到泛型一樣。JIT編譯器可以確保參數正確性,實施類型安全檢查,甚至執行類型特定的IntelliSense。

  當.net將泛型IL代碼編譯為本機代碼時,所產生的本機代碼取決於指定的類型。JIT編譯器跟蹤已經生成特定類型的IL代碼,如果本機指定的是值類型,則JIT編譯器將泛型類型參數替換為指定的值類型,並且將其編譯為本機代碼。如果JIT編譯器用已經編譯為本機代碼的值類型編譯泛型IL代碼,則只返回對IL代碼的引用。因為JIT編譯器在以后的所有場合中都將使用相同的值類型特定的IL代碼,所以不會存在代碼膨脹問題。如果本機指定的是引用類型,則JIT編譯器將泛型IL嗲沒種的泛型參數替換為object,並將其編譯為本機代碼。在以后的任何針對引用類型而不是泛型類型參數的請求中,JIT編譯器只會重新使用實際代碼,使用該代碼,實例仍然按照它們離開托管堆的大小分配空間,並且不會存在強制類型轉換。

泛型實現:

C#泛型支持包括類、結構、接口、委托四種泛型類型,以及其方法成員。此次,只以泛型類做簡單演示:

class Program
    {
        static void Main(string[] args)
        {
            int obj = 1;
            Test<int> testInt = new Test<int>(obj);
            Console.WriteLine(testInt.obj);//1

            string obj1 = "hello";
            Test<string> testString = new Test<string>(obj1);
            Console.WriteLine(testString.obj);//hello

            Console.Read();
        }
    }

    class Test<T>
    {
        public T obj;
        public Test(T obj)
        {
            this.obj = obj;
        }
    }
View Code

泛型約束:

  在指定一個類型參數時,可以指定類型參數必須滿足的約束條件。這里通過指定類型參數時使用where子句來實現的。泛型約束有:基類約束、接口約束、構造函數約束、引用類型和值類型約束。下面簡單一一介紹:

基類約束:

使用基類約束,可以指定某個類型實參必須繼承的基類。
基類約束有兩個功能:
(1)它允許在泛型類中使用由約束指定的基類所定義的成員。例如,可以調用基類的方法或者使用基類的屬性。如果沒有基類約束,編譯器就無法知道某個類型實參擁有哪些成員。通過提供基類約束,編譯器將知道所有的類型實參都擁有由指定基類所定義的成員。
(2)確保類型實參支持指定的基類類型參數。這意味着對於任意給定的基類約束,類型實參必須要么是基類本身,要么是派生於該基類的類,如果試圖使用沒有繼承指定基類的類型實參,就會導致編譯錯誤。
基類約束使用下面形式的where子句:where T:base-class-name
T是類型參數的名稱,base-class-name是基類的名稱,這里只能指定一個基類。

class A
    {
        public void Func1()
        { }
    }
 
    class B
    {
        public void Func2()
        { }
    }
 
    class C<S, T>
        where S : A
        where T : B
    {
        public C(S s,T t)
        {
            //S的變量可以調用Func1方法
            s.Func1();
            //T的變量可以調用Func2方法
            t.Func2();
        }
    }
View Code


接口約束:

接口約束用於指定某個類型參數必須應用的接口。接口的兩個主要功能和基類約束完全一樣。基本形式 where T:interface-name
interface-name是接口的名稱,可以通過使用由逗號分割的列表來同時指定多個接口。如果某個約束同時包含基類和接口,則先指定基類列表,再指定接口列表。

interface IA<T>
    {
        T Func1();
    }
 
    interface IB
    {
        void Func2();
    }
 
    interface IC<T>
    {
        T Func3();
    }
 
    class MyClass<T, V>
        where T : IA<T>
        where V : IB, IC<V>
    {
        public MyClass(T t,V v)
        {
            //T的對象可以調用Func1
            t.Func1();
            //V的對象可以調用Func2和Func3
            v.Func2();
            v.Func3();
        }
    }
View Code

 

構造器約束:
new()構造函數約束允許開發人員實例化一個泛型類型的對象。
一般情況下,我們無法創建一個泛型類型參數的實例。然而,new()約束改變了這種情況,它要求類型參數必須提供一個無參數的構造函數。在使用new()約束時,可以通過調用該無參構造函數來創建對象。基本形式: where T : new()
使用new()約束時應注意兩點:
(1)它可以與其他約束一起使用,但是必須位於約束列表的末端。
(2)new()僅允許開發人員使用無參構造函數來構造一個對象,即使同時存在其他的構造函數。換句話說,不允許給類型參數的構造函數傳遞實參。

class A
        {
            public A()
            { }
        }
 
        class B
        {
            public B()
            { }
            public B(int i)
            { }
        } 
        class C<T> where T : new()
        {
            T t;
            public C()
            {
                t = new T();
            }
        }
 
        class D
        {
            public void Func()
            {
                C<A> c = new C<A>();
                C<B> d = new C<B>();
            }
        }
View Code

值類型和引用類型約束:
如果引用類型和值類型之間的差別對於泛型代碼非常重要,那么這些約束就非常有用。 基本形式: where T : class where T : struct 若同時存在其他約束的情況下,class或struct必須位於列表的開頭。另外可以通過 使用約束來建立兩個類型參數之間的關系 例如 class GenericClass2<T, V> where V:T{} -------- 要求V必須繼承於T,這種稱為裸類型約束(naked type constraint)。

public struct A { }
        public class B { }
 
        public class C<T> where T : struct
        {
 
        }
 
        C<A> c1 = new C<A>();
        C<B> c2 = new C<B>();//error
View Code

 

泛型的優點:
  泛型使代碼可以重用,類型和內部數據可以在不導致代碼膨脹的情況下進行更改,而不管是值類型還是引用類型。可以一次性地開發、測試和部署代碼,通過任何類型(包括將來的類型)來重用它,並且全部具有編譯器支持和類型安全。因為泛型代碼不會強行對值類型進行裝箱和取消裝箱,或者對引用類型進行向下強制類型轉換,所以性能得到顯著提高。對於值類型,性能通常會提高200%;對於引用類型,在訪問該類型時,也可以達到預期性。

簡單來說:使用泛型,可以極大地提高代碼的重用度,同時還可以獲得強類型的支持,避免了隱式的裝箱、拆箱,在一定程度上提升了應用程序的性能。

再簡單點:可重用、類型安全、高效率。

 

 補充:

1、C#的泛型能力由CLR在運行時支持,它既不同於C++在編譯時所支持的靜態模板,也不同於Java在編譯器層面使用“擦拭法”支持的簡單的泛型。
2、C#的泛型支持包括類、結構、接口、委托四種泛型類型,以及方法成員。
3、C#的泛型采用“基類,接口,構造器,值類型/引用類型”的約束方式來實現對類型參數的“顯式約束”,它不支持C++模板那樣的基於簽名的隱式約束。

 

網上找到一個介紹泛型的好文章:

從簡單的例子理解泛型

 

引用:

C#泛型簡介

C#泛型編程

泛型約束

 


免責聲明!

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



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