在 C# 開發中,選擇正確的集合類型對效能與程式碼品質至關重要。從早期的陣列,到非泛型的 ArrayList,再到泛型 List<T>,每一代都在解決前一代的痛點。本文將深入剖析三者的記憶體模型、型別安全性、效能特徵與適用場景。
陣列是 C# 中最基礎、也是最早出現的集合結構。它在記憶體中佔據一塊連續區塊,這賦予了它極快的索引存取速度(O(1))。
arr[0] = "value" 直觀易懂。string[] names = new string[3];
names[0] = "Alice";
names[1] = "Bob";
names[2] = "Charlie";
// 修改
names[1] = "Bob Updated";
IndexOutOfRangeException。Add、Remove、Contains 等常用操作。ArrayList 位於 System.Collections 命名空間,內部以陣列為底層存儲,並在容量不足時自動擴容(通常翻倍)。它實作了 IList 介面,提供豐富的增刪改查方法。
ArrayList list = new ArrayList();
list.Add("Hello"); // 新增字串
list.Add(123); // 新增整數(隱含裝箱)
list[1] = 456; // 修改
list.RemoveAt(0); // 移除首項
list.Insert(0, "World");// 插入至開頭
object,編譯器無法阻止你將 int、string、自訂類別混雜存放。int)轉為 object 參考類型,在堆上配置記憶體並複製值。object 取回值類型,需型別檢查並複製回棧。(int)list[0] 若實際是字串,會拋出 InvalidCastException。// 裝箱:int → object
int val = 123;
object obj = val; // 隱含裝箱
// 拆箱:object → int
object obj2 = 456;
int val2 = (int)obj2; // 顯式拆箱,需型別檢查
兩者皆涉及記憶體配置與複製,效能成本遠高於直接操作值類型。
C# 2.0 引入泛型後,List<T> 成為 ArrayList 的泛型等效類。它同樣實作 IList<T>,API 幾乎一致,但關鍵差異在於編譯期強制型別。
List<int> numbers = new List<int>();
numbers.Add(100); // 僅接受 int
numbers.Add(200);
// numbers.Add("abc"); // 編譯錯誤:無法將 string 轉換為 int
numbers[0] = 300; // 直接存取,無拆箱
int first = numbers[0]; // 直接賦值,無裝箱
InvalidCastException。List<Customer> 明確表達集合語意,IDE 智慧提示完整。| 特性 | Array (T[]) | ArrayList | List<T> |
|---|---|---|---|
| 記憶體佈局 | 連續 | 連續 (Object[]) | 連續 (T[]) |
| 長度可變 | 否 | 是 | 是 |
| 型別安全 | 是 (編譯期) | 否 (執行期) | 是 (編譯期) |
| 值類型裝箱 | 無 | 頻繁 | 無 |
| 索引存取速度 | 最快 | 較慢 (需轉型) | 極快 (近似陣列) |
| 適用場景 | 固定大小、高效能數值運算 | 舊版相容、極少數動態型別場景 | 現代 C# 首選 |
List<T>:除非有極特殊理由(如互操作、固定大小緩衝區),否則優先使用泛型清單。ArrayList:僅維護 .NET Framework 1.x 遺留專案時接觸;新專案嚴禁引入。new List<int>(10000) 可減少擴容次數,提升大量資料載入效能。IReadOnlyList<T>:對外暴露介面而非具體類別,防止誤修改。從陣列到 ArrayList 再到 List<T>,C# 集合演進的核心線索是在保持動態擴容便利性的同時,消除型別不安全與效能損耗。理解底層記憶體模型(連續陣列 vs 物件參考)、裝箱拆箱機制,能幫助你在架構設計與效能調優時做出精準決策。現代 C# 開發中,List<T> 應是 90% 以上集合場景的標準答案。