泛型,型別參數,泛型條件約束介紹
訂閱 375
泛型
泛型可用於推遲資料型別的確定,這項工作在正常的定義過程中往往需要立即進行。比如,在定義類別時,你需要指定欄位的資料型別,方法參數的資料型別等。
為何使用泛型?
很明顯,泛型減少了設計層面的資料型別轉換,同時保持了程式碼的適用性和簡便性。
型別參數
泛型使用型別參數來代替本應被確定的資料型別,雖然也被稱為參數,但型別參數的作用類似於預留位置。在對使用了泛型的目標進行具現化或呼叫時,需要給出型別參數,比如 C# 中常見的泛型類別List<T>
,將其型別參數T
取代為某種具體的資料型別後,具現化才能進行。
下面的 C# 程式碼具現化了泛型類別List<T>
。
// 型別參數 T 被指定為 string
List<string> nicknames = new();
泛型方法
如果你希望定義一個泛型方法,那麽該方法需要擁有自己的型別參數,他不能與類別或介面的型別參數相同,如果類別或介面也運用了泛型的話。
下面的 C# 類別Box
擁有一個泛型方法GetFirst
,用於尋找第一個資料型別與T
一致的元素。
// 類別 Box,表示儲存內容的箱子
class Box
{
// 欄位 items,一個簡單的陣列
object[] items;
// 建構子,可以初始化 items
public Box(params object[] i)
{
items = i ?? Array.Empty<object>();
}
// 泛型方法 GetFirst,取得陣列中第一個型別為 T 的元素
public T? GetFirst<T>()
{
// 如果元素的型別與 T 一致,則傳回
foreach (object item in items)
if (item.GetType() == typeof(T))
return (T)item;
return default;
}
}
使用泛型方法GetFirst
尋找第一個字串,整數,浮點數型別的元素。
// 建立 Box 的執行個體 box
Box box = new("第一個字串", 123, "第二個字串", 234, 0.123f, 0.234f);
// 取得 box 中的第一個字串
Console.WriteLine(box.GetFirst<string>());
// 取得 box 中的第一個整數
Console.WriteLine(box.GetFirst<int>());
// 取得 box 中的第一個浮點數
Console.WriteLine(box.GetFirst<float>());
第一個字串
123
0.123
泛型條件約束
泛型條件約束主要用於限製泛型的使用,比如,限製型別參數可以指定的資料型別。對型別參數加以約束是理所當然的,因為他給予了開發人員很大的選擇範圍,沒人願意型別參數被指定為一種無法被處理的資料型別。
我們為GetFirst
方法的型別參數T
增加約束,使其只能被指定為參考型別,之前書寫的box.GetFirst<int>()
和box.GetFirst<float>()
將產生錯誤。
public T? GetFirst<T>() where T : class
{
// …
}
泛型的處理
對於接下來講述的內容,我們作出如下假設,語言包含泛型類別List<T>
,以及參考型別object
,實值型別int
,float
。
在程式碼編譯期間,泛型可能會以異構或同構的方式被處理,這取決於具體的語言編譯器。
異構會為不同的型別參數組建不同的程式碼,如果你使用了List<int>
和List<float>
,那麽編譯器需要為他們產生兩個不同的類別,以將型別參數T
分別取代為int
和float
。
同構會將泛型的型別參數轉換為最寬泛的資料型別,比如,無論你書寫List<int>
還是List<float>
,編譯器都會將型別參數T
取代為object
,因此,相對於異構,同構產生的類別只有一個而不是多個。在這種情況下,編譯器可能還需要調整某些程式碼,以完成額外的型別轉換,畢竟,在開發人員書寫的程式碼中,List<int>
或List<float>
可以直接關聯int
或float
型別的值,這些值的型別與編譯階段出現的object
型別存在轉換的必要。
為何采用異構的泛型能夠改善效率?
假如資料型別的轉換將消耗大量資源,比如,參考型別與實值型別之間的相互轉換,那麽采用異構的泛型會是一種很好的紓困方案,因為他使得相關的轉換不再存在。
上述中的資料型別轉換之所以會發生,是由於設計者希望采用一種寬泛的標準來涵蓋所有的可能,比如,使用陣列object[]
來儲存所有資料型別的值,當你將一個整數型別的值存入陣列,或將其從陣列中取出以進行算術運算時,參考型別object
與實值型別int
之間的轉換就會發生。而這樣的情況會在使用異構泛型後消失,因為最終建置的陣列是int[]
而非object[]
,將int
型別的值存入int[]
當然不需要任何轉換。
堆疊,堆積
要想深入了解實值型別和參考型別,你可以檢視堆積和參考一段。