Dictionary vs Hashtable: The Battle of Collections in C#
\n\nImagine this situation: you are writing code in C#, and you need to quickly store and retrieve data by key. You open the documentation and see two similar classes — Dictionary and Hashtable. Both work on the "key-value" principle, both promise lightning-fast data access. But which one to choose?
If you've asked yourself this question, you are not alone. On Stack Overflow, the topic "Why is Dictionary preferred over Hashtable in C#?" has gathered thousands of views and hundreds of votes. And today we will settle this question once and for all. Spoiler: Dictionary almost always wins. But let's break down why.
\n\nA Brief History Lesson: Where Did Hashtable Come From?
\n\nTo understand the essence of this rivalry, we need to look into the history of C#. When the .NET platform first appeared (version 1.0), developers had access to the Hashtable class from the System.Collections namespace. It was a classic hash table that could store any objects. Sounds cool, right? But there was one problem — lack of typing.
With the release of .NET 2.0 and the introduction of generics, Microsoft introduced a new collection — Dictionary<TKey, TValue> from the System.Collections.Generic namespace. And that's when the real magic began.
Type Safety: The Main Advantage of Dictionary
\n\nLet's look at a specific code example. Here's how working with Hashtable looks:
// Old way: Hashtable\nHashtable hashtable = new Hashtable();\nhashtable.Add("key1", 123);\nhashtable.Add("key2", "string"); // Error? No, it will compile!\nhashtable.Add(456, "another string"); // The key can also be any object\n\n// Now let's try to retrieve the data\nint value = (int)hashtable["key2"]; // Oops! InvalidCastException at runtime!\n\nSee the problem? Hashtable accepts any types — for both keys and values. The compiler will let any error through, and you'll only find out about it during program execution. This is a direct path to bugs that are hard to catch.
\n\nNow let's look at Dictionary:
\n\n// New, correct way: Dictionary\nDictionary<string, int> dictionary = new Dictionary<string, int>();\ndictionary.Add("key1", 123);\n// dictionary.Add("key2", "string"); // Compilation error! Type mismatch\n// dictionary.Add(456, "another string"); // Also an error — key must be string\n\n// Retrieve data without casting\nint value = dictionary["key1"]; // Clean, safe, elegant\n\nDictionary is type-safe. You explicitly specify what types the keys and values will be. The compiler checks this at build time. No unexpected runtime exceptions — the code either works or doesn't compile.
\n\nPerformance: Which is Faster?
\n\nThe second important aspect is performance. And here, Dictionary pulls ahead again. Why? It's all about boxing and unboxing.
\n\nWhen you work with Hashtable, all value types (int, double, struct, etc.) are boxed into objects. This creates overhead for the garbage collector and slows down execution. Let's test this in practice:
\n\n// Hashtable: boxing/unboxing\nHashtable ht = new Hashtable();\nStopwatch sw = Stopwatch.StartNew();\nfor (int i = 0; i < 1000000; i++)\n{\n ht.Add(i, i); // Each int is boxed into object\n}\nsw.Stop();\nConsole.WriteLine($"Hashtable adding: {sw.ElapsedMilliseconds} ms");\n\n// Dictionary: no boxing\nDictionary<int, int> dict = new Dictionary<int, int>();\nsw.Restart();\nfor (int i = 0; i < 1000000; i++)\n{\n dict.Add(i, i); // No boxing — working with int directly\n}\nsw.Stop();\nConsole.WriteLine($"Dictionary adding: {sw.