.NET 基礎類庫大量使用泛型:List<T>、Dictionary<TKey, TValue>、Nullable<T>(即 T?)。但許多開發者僅停留在「使用」層面,未嘗試「定義」屬於自己的泛型。本文從語法基礎、default 關鍵字、型別約束、實戰範例(農場動物體系)四個維度,帶您掌握自訂泛型的核心技能。
// 單一型別參數
public class MyGenericClass<T> { }
// 多型別參數
public class MyGenericClass<T1, T2, T3>
{
private T1 _t1Object;
public MyGenericClass(T1 item) => _t1Object = item;
public T1 T1Object => _t1Object;
}
型別參數命名慣例:單字母 T、TKey、TValue 或描述性名稱 TEntity、TDto。
// 編譯錯誤:不知道 T 是否有公用無參建構函式
public MyGenericClass() { _t1Object = new T1(); }
public MyGenericClass() { _t1Object = default(T1); }
// 值型別 → 0/false/結構體零值;參考型別 → null
透過 where 限制型別參數,獲得編譯期型別安全與 IntelliSense 提示:
| 約束語法 | 含義 |
|---|---|
where T : struct | 值型別(排除 Nullable) |
where T : class | 參考型別 |
where T : new() | 有公用無參建構函式 |
where T : BaseClass | 繼承自 BaseClass |
where T : IInterface | 實作介面 |
where T : unmanaged | 非受控型別 (C# 7.3+) |
where T : notnull | 非可為 null 型別 (C# 8+) |
where T : default | 覆寫預設約束 (C# 9+) |
多型別參數各自約束:class MyClass<T1, T2> where T1 : class where T2 : new()
public abstract class Animal
{
public string Name { get; set; }
public Animal() => Name = "The animal with no name";
public Animal(string name) => Name = name;
public void Feed() => Console.WriteLine($"{Name} has been fed.");
public abstract void MakeANoise();
}
public class Cow : Animal
{
public Cow(string name) : base(name) { }
public override void MakeANoise() => Console.WriteLine($"{Name} says 'moo!'");
}
public class Chicken : Animal
{
public Chicken(string name) : base(name) { }
public override void MakeANoise() => Console.WriteLine($"{Name} says 'cluck'");
}
public class SuperCow : Cow
{
public SuperCow(string name) : base(name) { }
public void Fly() => Console.WriteLine($"{Name} is flying!");
public override void MakeANoise() => Console.WriteLine($"{Name} says 'I am supercow!'");
}
public class Farm<T> : IEnumerable<T> where T : Animal
{
private readonly List<T> _animals = new List<T>();
public List<T> Animals => _animals;
public IEnumerator<T> GetEnumerator() => _animals.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _animals.GetEnumerator();
public void MakeNoises() => _animals.ForEach(a => a.MakeANoise());
public void FeedTheAnimals() => _animals.ForEach(a => a.Feed());
// 示範型別轉換與泛型回傳
public Farm<Cow> GetCows()
{
var cowFarm = new Farm<Cow>();
foreach (var a in _animals)
if (a is Cow c) cowFarm.Animals.Add(c);
return cowFarm;
}
}
var farm = new Farm<Animal>();
farm.Animals.Add(new Cow("Jack"));
farm.Animals.Add(new Chicken("Vera"));
farm.Animals.Add(new Chicken("Sally"));
farm.Animals.Add(new SuperCow("Kevin"));
farm.MakeNoises();
var dairyFarm = farm.GetCows();
dairyFarm.FeedTheAnimals();
foreach (var cow in dairyFarm)
if (cow is SuperCow sc) sc.Fly();
IEnumerable<out T> 允許 Farm<Cow> 隱性轉為 IEnumerable<Animal>;Action<in T> 相反。介面設計時善用 in/out 提升彈性。public TResult Convert<TSource, TResult>(TSource input),型別推論讓呼叫更簡潔。Func<T, TResult>、Action<T> 是最常用的泛型委派,LINQ 大量依賴它們。自訂泛型的關鍵三步驟:定義型別參數 → 加上必要約束 → 善用 default 與介面約束實現多型。農場範例展示了「以基類型別參數化集合、保留子類型別行為、支援型別安全篩選」的經典模式。掌握這套思維,您就能設計出如 Repository<TEntity>、Pipeline<TContext> 等高復用、強型別的核心架構元件。