泛型不僅提供語法便利性,更在型別安全與執行效能上帶來實質改善。本文透過 StandardClass(非泛型)與 GenericClass<T>(泛型)對比,深入剖析泛型如何解決傳統 object 基礎集合的三大痛點:型別不安全、裝箱/拆箱效能損耗、以及強制型別轉換風險。
// 一般類別使用 object 陣列
StandardClass C = new StandardClass(5);
C.AddItem("s1");
C.AddItem("s2");
C.AddItem("s3");
C.AddItem(1); // 編譯通過,但執行期隱患:整數混入字串集合
問題 1:型別不安全 - 編譯器無法阻止錯誤型別加入
問題 2:裝箱操作 - 值型別存入 object[] 會產生 boxing
// IL 代碼顯示裝箱指令
IL_0173: box [mscorlib]System.Int32 // 整數被裝箱為 object
// 泛型類別指定型別參數
GenericClass<string> gC = new GenericClass<string>(5);
gC.AddItem("s1");
gC.AddItem("s2");
gC.AddItem("s3");
// gC.AddItem(1); // 編譯錯誤 CS1503:無法將 int 轉換為 string
優勢 1:編譯期型別檢查 - 錯誤在開發階段即被攔截
優勢 2:避免裝箱/拆箱 - 陣列直接儲存指定型別 T[]
public object GetItem(int index) { return _items[index]; }
// 使用時必須轉型
string sHolder = (string)C.GetItem(1); // 可能拋出 InvalidCastException
// 若陣列實際存的是 double,轉型為 string 會在執行期失敗
public T GetItem(int index) { return _items[index]; }
// 使用時直接賦值,編譯器保證型別正確
string sHolder = gC.GetItem(1); // 安全,無需轉型
// int iHolder = gC.GetItem(1); // 編譯錯誤 CS0029:無法隱含轉換 string 為 int
public class GenericClass<T>
{
static int _count = 0;
int _maxItemCount;
T[] _items; // 關鍵:使用型別參數 T 而非 object
int _currentItem = 0;
public GenericClass(int items)
{
_count++;
_maxItemCount = items;
_items = new T[_maxItemCount];
}
public int AddItem(T item) // 參數型別為 T
{
if (_currentItem < _maxItemCount)
{
_items[_currentItem] = item;
return _currentItem++;
}
throw new Exception("Item queue is full");
}
public T GetItem(int index) // 回傳型別為 T
{
if (index >= _maxItemCount)
throw new ArgumentOutOfRangeException("index");
return _items[index];
}
// ...
}
| 指標 | StandardClass (object) | GenericClass<T> |
|---|---|---|
| 型別檢查時機 | 執行期 | 編譯期 |
| 值型別存取 | 需裝箱/拆箱 | 直接存取,零額外成本 |
| 取得元素 | 需強制轉型,可能失敗 | 型別安全,無需轉型 |
| 程式碼清晰度 | 充滿轉型語法 | 意圖明確,可讀性高 |
泛型透過「參數化型別」機制,將型別資訊保留至編譯期與執行期,徹底解決 object 基礎集合的三大缺陷。這不只是語法糖,更是 .NET 類型系統的根本進化。實務開發中,優先使用 List<T>、Dictionary<TKey, TValue> 等泛型集合,避免使用 ArrayList、Hashtable 等非泛型對應類別,能顯著提升程式碼品質與執行效能。