Tóm tắt nhanh
| Đặc điểm | Hashtable | Dictionary<TKey, TValue> |
| Generics | Non-generic (object key/value) | Generic (type-safe) |
| Namespace | System.Collections | System.Collections.Generic |
| Type Safety | Không (cast cần thiết) | Có (compile-time) |
| Boxing/Unboxing | Có (value types) | Không |
| Performance | Chậm hơn (boxing + cast) | Nhanh hơn |
| Null Key | Cho phép | Không cho phép (throw ArgumentNullException) |
| Null Value | Cho phép | Cho phép (reference types) |
| Missing Key Behavior | Trả về null | Throw KeyNotFoundException |
| Enumeration Order | Không đảm bảo | Insertion order (từ .NET Core 3.0+) |
| Thread Safety | Hashtable.Synchronized() wrapper | Không thread-safe (dùng ConcurrentDictionary) |
Ví dụ Hashtable (Legacy)
using System.Collections;
var ht = new Hashtable();
ht.Add(1, "One"); // int → object (boxing)
ht.Add("Key", 100); // string key, int value
ht.Add(DateTime.Now, "Time"); // object key
// Truy xuất - CẦN CAST
string s = (string)ht[1]; // Unboxing + cast
int i = (int)ht["Key"]; // Unboxing + cast
// Key không tồn tại
var missing = ht[999]; // null - không throw exception
// Enumeration
foreach (DictionaryEntry entry in ht)
{
Console.WriteLine($"{entry.Key} ({entry.Key.GetType()}): {entry.Value}");
}
Ví dụ Dictionary (Modern)
using System.Collections.Generic;
var dict = new Dictionary();
dict.Add(1, "One");
// dict.Add("Key", 100); // ❌ Compile error: type mismatch
// Truy xuất - TYPE SAFE
string s = dict[1]; // string trực tiếp
// Key không tồn tại
// var missing = dict[999]; // ❌ Throw KeyNotFoundException
// Safe access
if (dict.TryGetValue(999, out var value))
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("Not found");
}
// Hoặc C# 7+
dict.TryGetValue(999, out string value); // value = null nếu không tìm thấy
// Enumeration - giữ insertion order (.NET Core 3.0+)
foreach (var kvp in dict)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
Boxing/Unboxing Cost Demo
using System.Diagnostics;
const int N = 1_000_000;
// Hashtable - boxing mỗi Add, unboxing mỗi Get
var ht = new Hashtable();
var sw = Stopwatch.StartNew();
for (int i = 0; i < N; i++) ht.Add(i, i);
for (int i = 0; i < N; i++) _ = (int)ht[i];
Console.WriteLine($"Hashtable: {sw.ElapsedMilliseconds} ms");
// Dictionary - no boxing
var dict = new Dictionary();
sw.Restart();
for (int i = 0; i < N; i++) dict.Add(i, i);
for (int i = 0; i < N; i++) _ = dict[i];
Console.WriteLine($"Dictionary: {sw.ElapsedMilliseconds} ms");
// Kết quả điển hình: Hashtable ~200ms, Dictionary ~50ms (4x nhanh hơn)
Thread Safety
// Hashtable - có wrapper synchronized (legacy)
var syncHt = Hashtable.Synchronized(new Hashtable());
// Chỉ các method đơn lẻ thread-safe, compound operations KHÔNG
// Dictionary - KHÔNG thread-safe
// Multi-thread read OK, write cần lock
lock (dict) { dict[key] = value; }
// Modern: ConcurrentDictionary (lock-free, high perf)
using System.Collections.Concurrent;
var cd = new ConcurrentDictionary();
cd.TryAdd(1, "One");
cd.AddOrUpdate(1, "New", (k, v) => v + " Updated");
cd.GetOrAdd(2, k => "Computed");
Khi nào dùng Hashtable?
- Legacy code (.NET Framework 1.x/2.x).
- COM Interop, old APIs yêu cầu
IDictionary non-generic.
- Cần null key (hiếm).
- Deserialize dữ liệu cũ không có type info.
Khi nào dùng Dictionary?
- Mọi code mới (.NET 2.0+).
- Cần type safety, performance.
- Generic collections standard.
Dictionary Alternatives
| Type | Use Case |
ConcurrentDictionary | Multi-thread read/write |
SortedDictionary | Sorted by key, O(log n) |
SortedList | Sorted, index access, small data |
ImmutableDictionary | Functional, thread-safe, persistent |
FrozenDictionary | Read-heavy, ultra-fast lookup (.NET 8+) |
ConditionalWeakTable | Attach data to objects (weak keys) |
Migration Hashtable → Dictionary
// Hashtable
var ht = new Hashtable { { "a", 1 }, { "b", 2 } };
// Convert to Dictionary
var dict = ht.Cast()
.ToDictionary(de => (string)de.Key, de => (int)de.Value);
// Or manual
var dict2 = new Dictionary();
foreach (DictionaryEntry entry in ht)
{
dict2[(string)entry.Key] = (int)entry.Value;
}