C# 泛型

1、泛型安全

假設我要寫個公用的輸出傳入參數的方法(不用泛型),由於萬物皆對象的理由,我先定義一個方法show(object obj),以下面所示:架構

        public static void Show(object obj)
        {
            Console.WriteLine(obj.ToString());
        }

執行這個方法ide

            int i = 1;  //裝箱
            Show(i);

若是傳入的是值類型,值類型轉換爲引用類型,咱們知道會發生裝箱,這是對性能的損害,想一想若是是個集合,就得屢次執行裝箱、拆箱操做。如ArrayList類,ArrayList儲存對象,Add()方法定義爲須要把一個對象做爲參數,若是傳入的值類型,就得裝箱,在讀取ArrayList中的值時,又得進行拆箱,以下面代碼所示:函數

            var list = new ArrayList();
            list.Add(1); //裝箱

            foreach (int i in list)
            {
                Console.WriteLine(i); //拆箱
            }

若是使用泛型,就不會出現這樣的問題了,咱們使用List<T>類來改造上面代碼:性能

            var list = new List<int>();
            list.Add(1); 

            foreach (int i in list)
            {
                Console.WriteLine(i); 
            }

這裏就不存在裝箱和拆箱了,因此咱們在使用集合的時候,儘可能使用泛型集合,不要使用非泛型集合。ui

2、類型安全spa

在上面ArrayList類中,添加參數時,能夠添加任何對象,好比上面的例子,若是在添加整數類型後再添加引用類型,這麼作在編譯時是沒有任何問題,可是在foreach語句使用整數類型迭代的時候就會報錯。code

            var list = new ArrayList();
            list.Add(1); //裝箱
            list.Add("string");

            foreach (int i in list)
            {
                Console.WriteLine(i);
            }

這時候就會報InvalidCastException的異常。對象

若是使用泛型集合List<T>的時候去重寫上面的代碼,在編譯的時候就會報錯。因此這個地方咱們就能知道,泛型是在編譯時就已經執行了,因此係統運行時咱們時沒有裝箱拆箱的系統開銷,而非泛型是在運行時執行的,因此可能致使異常發生;blog

3、建立泛型類和泛型方法

泛型方法,從我最早第一個例子Show(object)  ,採用泛型來重寫,定義爲Show<T>(T);

        public static void Show<T>(T obj)
        {
            Console.WriteLine(obj.ToString());
        }

 泛型類,如public class List<T>{}

3.1 命名約定

  •       泛型類型的名稱用字母T做爲前綴。
  •       若是沒有特殊的要求,泛型類型運行用任意類替代,且只使有一個泛型類型,就能夠用字符T做爲泛型類型的名稱。
  •       若是泛型類型有特殊的要求(如它必須實現一個接口或派生自基類),或者使用了兩個或以上的泛型類型,就應給泛型類型使用描述性的名稱:

      public delegate void EventHandler<TEventArgs>(object sender,TEventArgs e);

      public delegate TOutput Convert<TInput,TOutput>(TInput input);

      public class SortedList<TKey,TValue>{};

3.2 默認值

     在泛型類和泛型方法中產生的一個問題是,在預先未知如下狀況時,如何將默認值分配給參數化類型 T,給定參數化類型 T 的一個變量 t,只有當 T 爲引用類型時,語句 t = null 纔有效;只有當 T 爲數值類型而不是結構時,語句 t = 0 才能正常使用。 解決方案是使用 default 關鍵字,此關鍵字對於引用類型會返回 null,對於數值類型會返回零。 對於結構,此關鍵字將返回初始化爲零或 null 的每一個結構成員。

使用方式如:T obj=default(T);

3.3 約束

在定義泛型類時,能夠對客戶端代碼可以在實例化類時用於類型參數的類型種類施加限制。 若是客戶端代碼嘗試使用某個約束所不容許的類型來實例化類,則會產生編譯時錯誤。 這些限制稱爲約束。 約束是使用where上下文關鍵字指定的。  下表列出了六種類型的約束:

約束   說明
where T:struct   對於結構的約束,類型T必須是值類型。
where T:class   類的約束,類型T必須是應用類型。
where T:<接口名稱> 類型參數必須是指定的接口或實現指定的接口。 能夠指定多個接口約束。 約束接口也能夠是泛型的。
where T:<基類名> 類型參數必須是指定的基類或派生自指定的基類。
where T:new()     類型參數必須具備無參數的公共構造函數。 當與其餘約束一塊兒使用時,new() 約束必須最後指定。
where T1:T2           類型T1必須是類型T2或派生自泛型類型T2,該約束也稱爲裸型約束。

 

 

 

 

 

 

 

 

    public class MyClass<T> where T : IComparer<T>, new()
    {

    }

上面代碼,使用泛型類型添加了兩個約束,聲明指定類型T必須實現了IComparer接口,且必須有一個默認構造函數

    public class MyClass<TOutput, TInput> where TOutput : IComparer<TOutput>, new()
        where TInput:class,TOutput
    {

    }

上面代碼用了兩個泛型類型,TOutput必須實現了IComparer接口,且必須有一個默認構造函數,TInput必須是引用類型,且類型必須是TOutput或派生自TOutput。

3.4 繼承

泛型類型能夠實現泛型接口,也能夠派生自一個類。泛型類型能夠派生自泛型基類,其要求必須重複接口的泛型類型,或者必須指定基類的類型。以下列所示:

    public class BaseClass<T> { }

    ///必須重複接口\基類的泛型類型
    public class MyClass<T> : BaseClass<T> { }
    public class BaseClass<T> { }

    ///必須指定基類的類型
    public class MyClass<T> : BaseClass<String> { }

派生類能夠是泛型類或非泛型類,例如定義一個抽象的泛型基類,它在派生類中用一個具體的類型實現,以下列所示:

    public abstract class Calcu<T>
    {
        public abstract T Add(T x, T y);

        public abstract T Sub(T x, T y);
    }

    /// <summary>
    /// 派生類中具體的類型實現
    /// </summary>
    public class IntCalcu : Calcu<int>
    {

        public override int Add(int x, int y)
        {
            return x + y;
        }

        public override int Sub(int x, int y)
        {
            return x - y;
        }
    }

4、結語

這些泛型類和泛型方法將一個或多個類型的指定推遲到客戶端代碼聲明並實例化該類或方法的時候。 例如,經過使用泛型類型參數 T,您能夠編寫其餘客戶端代碼可以使用的單個類,而不致引入運行時強制轉換或裝箱操做的成本或風險。在架構中有句話是讓一切能延遲的延遲。

相關文章
相關標籤/搜索