您好,登錄后才能下訂單哦!
一、泛型
假設我要寫個公用的輸出傳入參數的方法(不用泛型),因為萬物皆對象的理由,我先定義一個方法show(object obj),如下面所示:
public static void Show(object obj) { Console.WriteLine(obj.ToString()); }
執行這個方法
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); }
這里就不存在裝箱和拆箱了,所以我們在使用集合的時候,盡量使用泛型集合,不要使用非泛型集合。
二、類型安全
在上面ArrayList類中,添加參數時,可以添加任何對象,比如上面的例子,如果在添加整數類型后再添加引用類型,這么做在編譯時是沒有任何問題,但是在foreach語句使用整數類型迭代的時候就會報錯。
var list = new ArrayList(); list.Add(1); //裝箱 list.Add("string"); foreach (int i in list) { Console.WriteLine(i); }
這時候就會報InvalidCastException的異常。
如果使用泛型集合List<T>的時候去重寫上面的代碼,在編譯的時候就會報錯。所以這個地方我們就能知道,泛型是在編譯時就已經執行了,所以系統運行時我們時沒有裝箱拆箱的系統開銷,而非泛型是在運行時執行的,所以可能導致異常發生;
三、創建泛型類和泛型方法
泛型方法,從我最先第一個例子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; } }
四、結語
這些泛型類和泛型方法將一個或多個類型的指定推遲到客戶端代碼聲明并實例化該類或方法的時候。 例如,通過使用泛型類型參數 T,您可以編寫其他客戶端代碼能夠使用的單個類,而不致引入運行時強制轉換或裝箱操作的成本或風險。在架構中有句話是讓一切能延遲的延遲。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。