Dictionary vs Hashtable : la bataille des collections en C#
\n\nImaginez la situation : vous écrivez du code en C# et vous devez rapidement stocker et récupérer des données par clé. Vous ouvrez la documentation et voyez deux classes similaires — Dictionary et Hashtable. Les deux fonctionnent sur le principe "clé-valeur", les deux promettent un accès fulgurant aux données. Mais lequel choisir ?
Si vous vous êtes déjà posé cette question, vous n'êtes pas seul. Sur Stack Overflow, le sujet "Why is Dictionary preferred over Hashtable in C#?" a recueilli des milliers de vues et des centaines de votes. Et aujourd'hui, nous allons régler cette question une fois pour toutes. Spoiler : Dictionary gagne presque toujours. Mais voyons pourquoi.
\n\nPetit retour historique : d'où vient Hashtable ?
\n\nPour comprendre l'essence de cette opposition, il faut se plonger dans l'histoire de C#. Lorsque la plateforme .NET est apparue (version 1.0), les développeurs avaient accès à la classe Hashtable de l'espace de noms System.Collections. C'était une table de hachage classique, capable de stocker n'importe quel objet. Ça a l'air génial, non ? Mais il y avait un problème — l'absence de typage.
Avec la sortie de .NET 2.0 et l'apparition des génériques (generics), Microsoft a présenté une nouvelle collection — Dictionary<TKey, TValue> de l'espace de noms System.Collections.Generic. Et c'est là que la vraie magie a commencé.
Sécurité de type : le principal avantage de Dictionary
\n\nRegardons un exemple de code concret. Voici à quoi ressemble le travail avec Hashtable :
// Ancienne méthode : Hashtable\nHashtable hashtable = new Hashtable();\nhashtable.Add("clé1", 123);\nhashtable.Add("clé2", "chaîne"); // Erreur ? Non, ça compile !\nhashtable.Add(456, "encore une chaîne"); // La clé peut aussi être n'importe quel objet\n\n// Essayons maintenant de récupérer les données\nint value = (int)hashtable["clé2"]; // Oups ! InvalidCastException à l'exécution !\n\nVous voyez le problème ? Hashtable accepte n'importe quel type — aussi bien pour les clés que pour les valeurs. Le compilateur laissera passer n'importe quelle erreur, et vous ne la découvrirez qu'à l'exécution du programme. C'est un chemin direct vers des bugs difficiles à détecter.
\n\nRegardons maintenant Dictionary :
\n\n// Nouvelle méthode, correcte : Dictionary\nDictionary<string, int> dictionary = new Dictionary<string, int>();\ndictionary.Add("clé1", 123);\n// dictionary.Add("clé2", "chaîne"); // Erreur de compilation ! Le type ne correspond pas\n// dictionary.Add(456, "encore une chaîne"); // Erreur aussi — la clé doit être de type string\n\n// Récupérons les données sans conversion\nint value = dictionary["clé1"]; // Propre, sûr, élégant\n\nDictionary est type-safe. Vous spécifiez explicitement les types des clés et des valeurs. Le compilateur vérifie cela à la compilation. Pas d'exceptions inattendues à l'exécution — soit le code fonctionne, soit il ne compile pas.
\n\nPerformances : qui est le plus rapide ?
\n\nLe deuxième aspect important est la performance. Et là encore, Dictionary prend l'avantage. Pourquoi ? Tout est une question de boxing et unboxing.
\n\nLorsque vous travaillez avec Hashtable, tous les types valeur (int, double, struct, etc.) sont encapsulés dans des objets. Cela crée une charge pour le garbage collector et ralentit le travail. Vérifions cela en pratique :
\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); // Chaque int est encapsulé dans un object\n}\nsw.Stop();\nConsole.WriteLine($"Ajout Hashtable : {sw.ElapsedMilliseconds} ms");\n\n// Dictionary : sans boxing\nDictionary<int, int> dict = new Dictionary<int, int>();\nsw.Restart();\nfor (int i = 0; i < 1000000; i++)\n{\n dict.Add(i, i); // Pas de boxing — on travaille directement avec int\n}\nsw.Stop();\nConsole.WriteLine($"Ajout Dictionary : {sw.