概念:使用佔位符T來代表某種類型,編譯期間決定其具體類型
- 通過「參數化類型」,實現在同一份程式碼上操作多種數據類型
- 泛型可以修飾:類別、方法、委派等。
- 同一個方法來處理 傳入不同種型參數的辦法
效能比較: 泛型方法 > 普通方法 > object方法
泛型方法的性能最高,其次是普通方法,object方法的性能最低。- 除了方法可以是泛型以外,類別也可以是泛型的
- 除了可以有泛型類別,也可以有泛型介面,
- 也可以有泛型委派
注意:
- 泛型在聲明的時候可以不指定具體的類型,但是在使用的時候必須指定具體類型
- 如果子類別也是泛型的,那麼繼承的時候可以不指定具體類型
- 類別實作泛型介面也是這種情況
一、什麼是泛型
泛型是C#2.0推出的新語法,不是語法糖,而是2.0由框架升級提供的功能。
我們在程式設計程式時,常常會遇到功能非常相似的模組,只是它們處理的資料不一樣。但我們沒有辦法,只能分別寫多個方法來處理不同的資料型態。這時候,那麼問題來了,有沒有一種辦法,用同一個方法來處理傳入不同種型參數的辦法呢?泛型的出現就是專門來解決這個問題的。
二、為什麼要使用泛型
以下方法,因為方法宣告的時候,寫死了參數類型
1
2
3
4
5
6
7
8
9
10
11
12
//因為方法宣告的時候,寫死了參數類型
static void Show(string text) {
Console.WriteLine(text);
}
static void Show(int number) {
Console.WriteLine(number);
}
static void Show(DateTime date) {
Console.WriteLine(date);
}
從上面的結果我們可以看出這三個方法,除了傳入的參數不同外,裡面實現的功能都是一樣的。在1.0版的時候,還沒有泛型這個概念,那該怎麼辦呢?相信很多人會想到了OOP三大特性之一的繼承,我們知道,C#語言中,object是所有類型的基類,將上面的程式碼進行以下最佳化:
1
2
3
4
//object類型 會有boxing unboxing的效能問題
static void Show(object o) {
Console.WriteLine(o);
}
從上面的結果我們可以看出,使用Object類型達到了我們的要求,解決了程式碼的可重複使用。或許有人會問定義的是object類型的,為什麼可以傳入int、string等型別呢?原因有二:
object類型是一切類型的父類別。- 透過繼承,子類別擁有父類別的一切屬性和行為,任何父類別出現的地方,都可以用子類別來代替。
但是上面object類型的方法又會帶來另一個問題:裝箱和拆箱,會損耗程式的效能。
微軟在C#2.0的時候推出了泛型,可以很好的解決上面的問題。
三、泛型類型參數
可以解決同一個方法來處理 傳入不同類型參數
在泛型類型或方法定義中,類型參數是在其實體化泛型類型的變數時,客戶端指定的特定類型的佔位符。 泛型類別(GenericList<T>)無法按原樣使用,因為它不是真正的類型;它更像是類型的藍圖。 若要使用 GenericList<T>,客戶端程式碼必須透過指定尖括號內的類型參數來宣告並實例化建構型別。 此特定類別的類型參數可以是編譯器可識別的任何類型。 可建立任意數量的建構類型實例,其中每個使用不同的類型參數。
上面例子中的程式碼可以修改如下:
1
2
3
4
5
//泛型方法 (同一個方法來處理 傳入不同種型參數)
static void Show<T>(T value)
{
Console.WriteLine($"{value!.GetType()}: {value}");
}
調用:
1
2
3
4
5
6
7
string s = "rii test";
int i = 99;
DateTime date = DateTime.Now;
Show<string>(s);
Show<int>(i);
Show<DateTime>(date);
為什麼泛型可以解決上面的問題呢?
泛型是延遲聲明的:即定義的時候沒有指定特定的參數類型,把參數類型的聲明推遲到了調用的時候才指定參數類型。 延遲思想在程式架構設計的時候很受歡迎。例如:分散式快取佇列、EF的延遲載入等等。
泛型究竟是如何運作的呢?
控制台程式最後會編譯成一個exe程序,exe被點擊的時候,會經過JIT(即時編譯器)的編譯,最後產生二進位程式碼,才能被電腦執行。泛型加入到語法以後,VS自帶的編譯器又做了升級,升級之後編譯時遇到泛型,會做特殊的處理:產生佔位符。再次經過JIT編譯的時候,會把上面編譯產生的佔位符替換成具體的資料型別
泛型約束總共有五種:
泛型約束總共有五種
| 約束 | 說明 |
|---|---|
| T:結構 | 類型參數必須是值型 |
| T:類別 | 類型參數必須是參考類型;這一點也適用於任何類別、介面、委派或陣列類型。 |
T:new() | 類型參數必須具有無參數的公共建構函數。 當與其他約束一起使用時,new() 約束必須最後指定。 |
T:<基底類別名稱> | 類型參數必須是指定的基底類別或衍生自指定的基底類別。 |
T:<介面名稱> | 類型參數必須是指定的介面或實作指定的介面。 可以指定多個介面約束。 約束介面也可以是泛型的。 |