在深入泛型世界前,先理解非泛型集合類別的設計缺陷,有助於體會泛型解決的核心問題。本文剖析 StandardClass 這個典型的「以 object 為基礎」集合類別,展示其結構與運作方式,為後續泛型改良鋪路。
public class StandardClass
{
static int _count = 0; // 類別層級實例計數器
int _maxItemCount; // 最大容量
object[] _items; // 核心:object 陣列
int _currentItem = 0; // 目前元素數量
public StandardClass(int items)
{
_count++;
_maxItemCount = items;
_items = new object[_maxItemCount]; // 建立 object 陣列
}
// 加入元素:接受 object,任何型別皆可
public int AddItem(object item)
{
if (_currentItem < _maxItemCount)
{
_items[_currentItem] = item;
return _currentItem++;
}
throw new Exception("Item queue is full");
}
// 取得元素:回傳 object,呼叫端需自行轉型
public object GetItem(int index)
{
if (index >= _maxItemCount)
throw new ArgumentOutOfRangeException("index");
return _items[index];
}
public int ItemCount => _currentItem;
public override string ToString()
{
return $"There are {_count} instances of {this.GetType()} " +
$"which contains {_currentItem} items of type {_items.GetType()}";
}
}
屬於類別層級,記錄所有 StandardClass 實例總數。每次建構子呼叫遞增,展示類別生命週期追蹤機制。
核心設計決策:使用 object[] 陣列儲存元素。因為所有型別(參考型別與值型別)皆隱含繼承自 object,單一陣列即可容納任意型別混合。
// 參數與回傳皆為 object
public int AddItem(object item) { ... }
public object GetItem(int index) { ... }
這迫使呼叫端在取得元素時進行強制型別轉換:
string s = (string)stdClass.GetItem(0); // 執行期可能拋出例外
// 輸出範例
There are 2 instances of StandardClass which contains 3 items of type System.Object[]
注意:_items.GetType() 永遠回傳 System.Object[],無法得知實際儲存的元素型別。
| 問題 | 說明 | 後果 |
|---|---|---|
| 型別不安全 | 編譯器允許 AddItem(123) 加入整數到字串集合 |
執行期 InvalidCastException |
| 效能損耗 | 值型別每次存取皆產生裝箱/拆箱 | 記憶體壓力增加、CPU 週期浪費 |
| 程式碼雜亂 | 到處充滿 (Type)obj 強制轉型 |
可讀性差、維護困難、易出錯 |
StandardClass 展示了「以 object 為基礎」集合的極限:用極大的彈性換取了型別安全與效能。泛型的誕生,正是要在「保持彈性」與「獲得型別安全/效能」之間找到平衡點——透過參數化型別,讓編譯器與執行期都知道集合裝的是什麼型別。
StandardClass 是理解泛型動機的完美反面教材。它的每個設計選擇(object[]、object 參數/回傳),在泛型版本 GenericClass<T> 中都被對應的 T[]、T 參數、T 回傳所取代。這三處替換,構成了泛型類別設計的核心模式。