首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> C# 入門

C# Hashtable 與 ArrayList 深度解析:非泛型集合遺產、性能陷阱、泛型替代方案、現代最佳實踐

     編輯:C# 入門

`Hashtable` 與 `ArrayList` 是 .NET Framework 1.x 時代的非泛型集合標誌。雖然 .NET 2.0+ 引入泛型 (`Dictionary<TKey, TValue>`、`List<T>`) 徹底取代了它們,但在**維護舊代碼、反射/動態場景、特定序列化協議**中仍會遇到。本文深度對比原理、性能陷阱 (裝箱/拆箱)、線程安全誤區,並給出遷移指南。

一、核心區別總覽

特性HashtableArrayListDictionary<TKey,TValue>List<T>
類型系統非泛型 (`object` 鍵值)非泛型 (`object` 元素)泛型 (強類型)泛型 (強類型)
底層結構哈希表 (桶+鏈表/開放寻址)動態數組哈希表 (優化)動態數組
鍵值/元素類型任意 `object`任意 `object``TKey` / `TValue``T`
值類型存儲**裝箱****裝箱****無裝箱** (結構體內聯)**無裝箱**
查找複雜度O(1) 平均O(n) 線性O(1) 平均O(n) 線性 (索引 O(1))
線程安全僅 `Synchronized` 包裝器支持**單操作**原子性同上無內置線程安全無內置線程安全
鍵為 null允許 (一個)N/A允許 (若 `TKey` 為引用類型)N/A
鍵區分大小寫默認區分 (`IEqualityComparer`)N/A默認區分N/A

二、性能殺手:裝箱/拆箱 實測


using System.Diagnostics;

public static void BoxingBenchmark()
{
    const int Count = 1_000_000;
    var sw = Stopwatch.StartNew();

    // ArrayList 存 int (裝箱)
    var arrayList = new ArrayList();
    for (int i = 0; i < Count; i++) arrayList.Add(i);
    long alMem = GC.GetTotalMemory(true);
    sw.Stop();
    Console.WriteLine($"ArrayList Add: {sw.ElapsedMs}ms, Mem: {alMem/1024/1024}MB");
    // 每次 Add 分配一個 Int32 對象在堆上

    sw.Restart();
    // List 存 int (無裝箱)
    var list = new List(Count);
    for (int i = 0; i < Count; i++) list.Add(i);
    long listMem = GC.GetTotalMemory(true);
    sw.Stop();
    Console.WriteLine($"List Add: {sw.ElapsedMs}ms, Mem: {listMem/1024/1024}MB");
    // 內存佔用約為 ArrayList 1/3,速度快 3-5 倍
}

結論:高頻值類型操作,**非泛型集合嚴重拖垮性能與內存**。這是棄用它們的首要原因。

三、Hashtable 經典操作與陷阱


var ht = new Hashtable();
// 添加
ht.Add("id", 1001); // 鍵值均為 object
ht["name"] = "Tom"; // 索引器覆蓋/添加

// 遍歷 - 必須用 DictionaryEntry (鍵值對結構體)
foreach (DictionaryEntry de in ht)
{
    Console.WriteLine($"Key: {de.Key} (Type: {de.Key.GetType()}), Value: {de.Value} (Type: {de.Value.GetType()})");
    // 強制轉型
    if (de.Key is string key && de.Value is int val) { /* ... */ }
}

// 判斷鍵存在
bool has = ht.ContainsKey("id"); // 推薦
bool hasOld = ht.Contains("id"); // 同義,舊方法

// 刪除
ht.Remove("id");
ht.Clear();

⚠️ 線程安全陷阱


// Hashtable.Synchronized 返回包裝器,僅保證單個方法原子
var syncHt = Hashtable.Synchronized(new Hashtable());

// 多線程下「檢查後操作」非原子 - 競態條件
// 線程 A                    // 線程 B
if (!syncHt.ContainsKey("k"))  if (!syncHt.ContainsKey("k"))
    syncHt.Add("k", 1);          syncHt.Add("k", 1); // 可能拋異常或覆蓋

// 正確:自行加鎖
lock (syncHt.SyncRoot) // 使用包裝器暴露的 SyncRoot
{
    if (!syncHt.ContainsKey("k")) syncHt.Add("k", 1);
}

// 現代替代:ConcurrentDictionary (無鎖高性能)

四、ArrayList 經典操作與陷阱


var al = new ArrayList { 1, "two", 3.0, true }; // 混合類型 - 惡夢開始

// 取值必須轉型
int a = (int)al[0]; // 運行時若類型不匹配拋 InvalidCastException
string b = (string)al[1];

// 遍歷
foreach (object obj in al) { /* 處理 object */ }

// 插入/刪除 (O(n) 移動元素)
al.Insert(1, "inserted");
al.RemoveAt(2);
al.Remove("two"); // 刪除首個匹配項

// 排序 (要求元素實現 IComparable 或傳 IComparer)
al.Sort(); // 混合類型拋異常

五、遷移指南:非泛型 → 泛型

舊代碼模式現代替代關鍵修改點
`Hashtable ht = new Hashtable();``var dict = new Dictionary();`指定鍵值類型;若值確異構用 `object` 或定義基類/接口
`ht["key"] = value;``dict["key"] = value;`語法相同
`foreach (DictionaryEntry de in ht)``foreach (var kvp in dict)``kvp.Key`、`kvp.Value` 強類型
`ArrayList al = new ArrayList();``var list = new List();` 或 `List<具体类型>`指定元素類型
`al.Add(item);``list.Add(item);`類型檢查在編譯期
`(int)al[i]``list[i]`直接得 `int`,無轉型、無拆箱
`Hashtable.Synchronized``ConcurrentDictionary` / `ImmutableDictionary` / 自行 `lock`更高性能、更清晰語義

六、仍需使用非泛型集合的極少數場景

  1. .NET 1.x 老項目維護:無法升級框架。
  2. COM 互操作 / 某些舊版序列化器:要求具體類型 `Hashtable`/`ArrayList` (如 `XmlSerializer` 對 `IDictionary`/`IList` 支持有限)。
  3. 極度動態場景**:鍵/值類型運行時才確定、且極其多樣,無公共基類/接口 (如通用屬性包、腳本引擎綁定)。即便如此,`Dictionary`/`List` 通常更優。
  4. 反射調用舊 API**:參數類型為 `Hashtable`/`ArrayList`。
  5. 七、現代最佳實踐總結

    1. 新代碼零容忍:Code Review 直接駁回 `new Hashtable()`/`new ArrayList()`。
    2. 首選泛型:`Dictionary<TKey, TValue>`、`List<T>`、`HashSet<T>`、`ConcurrentDictionary`、`ImmutableDictionary`/`List` (系統編程)。
    3. 值類型零裝箱:泛型結構體內聯存儲,性能提升數倍。
    4. 編譯期類型安全:消除 `InvalidCastException` 運行時炸彈。
    5. 遺留代碼隔離**:在適配器層轉換為泛型,業務層純泛型。

    **口訣**:「非泛型是遺產,泛型是當下,併發用 Concurrent,不可變用 Immutable,裝箱拆箱是大敵,類型安全靠編譯。」

    1. 上一篇文章:
    2. 下一篇文章:
    Copyright © 程式碼之家 All Rights Reserved