When to Use Dictionary vs Hashtable in C#

Dictionary<TKey, TValue> is a generic, type-safe collection from modern C#. Hashtable is a non-generic legacy collection that stores objects and requires casting.

Use Dictionary<TKey, TValue> for all new code - it's type-safe, faster, and doesn't require boxing/unboxing. Only use Hashtable when working with legacy .NET Framework 1.x code or when you absolutely need thread-safe operations with lock-free reads.

Dictionary provides compile-time type checking and better performance due to no boxing of value types. Hashtable requires runtime type casting and is slower with value types.

Using Dictionary (Modern Approach)

Dictionary is generic and type-safe. It provides compile-time type checking and doesn't require casting.

C# Example Code
using System.Collections;
using System.Diagnostics;

// Dictionary - generic and type-safe
Console.WriteLine("=== Dictionary<TKey, TValue> - Modern approach ===");
Dictionary<string, int> ages = new Dictionary<string, int>();
ages.Add("Alice", 30);
ages.Add("Bob", 25);
ages["Charlie"] = 35;  // Add or update

// Type-safe access - no casting needed
int aliceAge = ages["Alice"];  // Returns int directly
Console.WriteLine($"Alice's age: {aliceAge}");

// Compile-time type checking
// ages.Add("Dave", "thirty");  // Compile error - wrong type!

Using Hashtable (Legacy Approach)

Hashtable is non-generic and requires casting. It stores everything as objects, leading to boxing/unboxing overhead with value types.

C# Example Code
// Hashtable - non-generic, legacy
Console.WriteLine("\n=== Hashtable - Legacy approach ===");
Hashtable legacyAges = new Hashtable();
legacyAges.Add("Alice", 30);
legacyAges.Add("Bob", 25);
legacyAges["Charlie"] = 35;

// Requires casting - error-prone
int aliceAgeLegacy = (int)legacyAges["Alice"];  // Must cast from object
Console.WriteLine($"Alice's age: {aliceAgeLegacy}");

// No compile-time type checking
legacyAges.Add("Dave", "thirty");  // Compiles but wrong type!

// Iteration differences
Console.WriteLine("\n=== Dictionary iteration ===");
foreach (KeyValuePair<string, int> kvp in ages)
{
    Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}

Console.WriteLine("\n=== Hashtable iteration ===");
foreach (DictionaryEntry entry in legacyAges)
{
    Console.WriteLine($"{entry.Key}: {entry.Value}");
}

Safe Value Access with TryGetValue

Dictionary provides TryGetValue for safe access without exceptions. Hashtable requires Contains checks before accessing.

C# Example Code
// TryGetValue - safer access
Console.WriteLine("\n=== Safe value access ===");
if (ages.TryGetValue("Eve", out int eveAge))
{
    Console.WriteLine($"Eve's age: {eveAge}");
}
else
{
    Console.WriteLine("Eve not found in Dictionary");
}

// Hashtable requires Contains check
if (legacyAges.Contains("Eve"))
{
    int age = (int)legacyAges["Eve"];
    Console.WriteLine($"Eve's age: {age}");
}
else
{
    Console.WriteLine("Eve not found in Hashtable");
}

Common Dictionary Operations

C# Example Code
// Common Dictionary operations
Console.WriteLine("\n=== Dictionary operations ===");
Console.WriteLine($"Count: {ages.Count}");
Console.WriteLine($"Contains key 'Bob': {ages.ContainsKey("Bob")}");
Console.WriteLine($"Contains value 30: {ages.ContainsValue(30)}");

ages.Remove("Bob");
Console.WriteLine($"After removing Bob, count: {ages.Count}");

// Keys and Values
Console.WriteLine("\nAll keys:");
foreach (string key in ages.Keys)
{
    Console.WriteLine($"  {key}");
}

Console.WriteLine("\nAll values:");
foreach (int value in ages.Values)
{
    Console.WriteLine($"  {value}");
}

Performance Comparison

C# Example Code
// Performance comparison
Console.WriteLine("\n=== Performance comparison ===");

const int iterations = 100000;
Stopwatch sw = new Stopwatch();

// Dictionary performance
sw.Start();
Dictionary<int, int> dict = new Dictionary<int, int>();
for (int i = 0; i < iterations; i++)
{
    dict[i] = i * 2;
}
sw.Stop();
Console.WriteLine($"Dictionary insert: {sw.ElapsedMilliseconds}ms");

// Hashtable performance
sw.Restart();
Hashtable hash = new Hashtable();
for (int i = 0; i < iterations; i++)
{
    hash[i] = i * 2;  // Boxing occurs here
}
sw.Stop();
Console.WriteLine($"Hashtable insert: {sw.ElapsedMilliseconds}ms");

Console.WriteLine("Dictionary is typically faster due to no boxing/unboxing");