.NET 2.0 引入泛型前,開發者只能用 System.Object 或非泛型集合(如 ArrayList)處理多型資料,導致頻繁的裝箱/拆箱與型別轉換,不僅效能損耗大,且編譯期無法發現型別錯誤。泛型的出現,提供了「虛擬泛型」的完美解法:
T)作為佔位符,首次編譯產生含佔位符的 IL;JIT 二次編譯時,依實際型別參數(如 string、int)替換佔位符,生成專屬原生代碼。// 定義泛型類別
class Human<T> {
public void Say(T word) {
Console.WriteLine(word.ToString());
}
}
// 使用時指定型別
var h = new Human<string>();
h.Say("Hello"); // T 被替換為 string
IL 中可見 Human`1<T> 與 Say(!!0),JIT 時 !!0 替換為具體型別。
ArrayList 允許任意型別混入,導致裝箱與執行期型別錯誤;List<T> 在定義時鎖定型別,編譯期即攔截錯誤:
var list = new List<int>();
list.Add("abc"); // 編譯錯誤:無法從 string 轉換為 int
list.Add(123); // 正確
這體現了「錯誤越早發現越好」的工程原則。
約束讓泛型類別能安全呼叫特定成員。語法:where T : 條件。
class:參考型別(類別、介面、委派、陣列)。struct:實值型別(排除 Nullable)。new():需有公用無參建構函式(需置最後)。基底類別名:繼承自指定類別。介面名稱:實作指定介面(可多個)。U:裸型別約束,T 必須為 U 或其衍生型別。interface IWeapon {
string WeaponName { get; }
int WeaponValue { get; }
}
class Sword : IWeapon { /* 實作 */ }
class Hero<T> where T : IWeapon, new() {
int _attack = 100;
public int AttackValue { get => _attack; set => _attack = value; }
public void AddArm(T weapon) {
_attack += ((IWeapon)weapon).WeaponValue;
}
}
// 使用
var hero = new Hero<Sword>();
hero.AddArm(new Sword()); // 攻擊力增加 500
約束確保 T 具備 IWeapon 成員與無參建構函式,編譯期保證型別安全。
C# 3.0 引入 var 隱含型別,編譯期由推斷決定實際型別,並非執行期動態,本質上仍屬靜態型別,與泛型的「編譯期具體化」不同,不宜視為「真泛型」。
泛型以「佔位符 + JIT 具體化」機制,在不犧牲效能與型別安全前提下,實現了高度可重用的演算法與資料結構。善用泛型集合與約束,是寫出健壯、高效 .NET 程式碼的關鍵。