Dictionary vs Hashtable: batalha de coleções em C#
\n\nImagine a situação: você está escrevendo código em C# e precisa salvar e recuperar dados rapidamente por chave. Você abre a documentação e vê duas classes semelhantes — Dictionary e Hashtable. Ambas funcionam no princípio "chave-valor", ambas prometem acesso ultrarrápido aos dados. Mas qual escolher?
Se você já se fez essa pergunta, não está sozinho. No Stack Overflow, o tópico "Why is Dictionary preferred over Hashtable in C#?" acumulou milhares de visualizações e centenas de votos. E hoje vamos resolver essa questão de uma vez por todas. Spoiler: Dictionary quase sempre vence. Mas vamos entender por quê.
\n\nUma breve viagem pela história: de onde veio o Hashtable?
\n\nPara entender a essência do confronto, precisamos olhar para a história do C#. Quando a plataforma .NET foi lançada (versão 1.0), os desenvolvedores tinham acesso à classe Hashtable do namespace System.Collections. Era uma tabela hash clássica que podia armazenar qualquer objeto. Parece legal, certo? Mas havia um problema — falta de tipagem.
Com o lançamento do .NET 2.0 e o surgimento dos genéricos (generics), a Microsoft apresentou uma nova coleção — Dictionary<TKey, TValue> do namespace System.Collections.Generic. E foi aí que a mágica começou.
Segurança de tipos: a principal vantagem do Dictionary
\n\nVamos ver um exemplo específico de código. Veja como é trabalhar com Hashtable:
// Jeito antigo: Hashtable\nHashtable hashtable = new Hashtable();\nhashtable.Add("chave1", 123);\nhashtable.Add("chave2", "string"); // Erro? Não, vai compilar!\nhashtable.Add(456, "mais uma string"); // A chave também pode ser qualquer objeto\n\n// Agora vamos tentar recuperar os dados\nint value = (int)hashtable["chave2"]; // Opa! InvalidCastException em tempo de execução!\n\nViu o problema? Hashtable aceita qualquer tipo — tanto para chaves quanto para valores. O compilador deixará passar qualquer erro, e você só descobrirá durante a execução do programa. Isso é um caminho direto para bugs difíceis de detectar.
\n\nAgora vejamos o Dictionary:
\n\n// Jeito novo e correto: Dictionary\nDictionary<string, int> dictionary = new Dictionary<string, int>();\ndictionary.Add("chave1", 123);\n// dictionary.Add("chave2", "string"); // Erro de compilação! Tipo incompatível\n// dictionary.Add(456, "mais uma string"); // Também erro — a chave deve ser string\n\n// Recuperando dados sem conversões\nint value = dictionary["chave1"]; // Limpo, seguro, elegante\n\nDictionary é type-safe. Você especifica explicitamente quais tipos serão usados para chaves e valores. O compilador verifica isso em tempo de compilação. Sem exceções inesperadas em tempo de execução — o código ou funciona, ou não compila.
\n\nDesempenho: quem é mais rápido?
\n\nO segundo aspecto importante é o desempenho. E aqui o Dictionary novamente sai na frente. Por quê? Tudo se resume ao boxing e unboxing.
\n\nQuando você trabalha com Hashtable, todos os tipos por valor (int, double, struct, etc.) são empacotados em objetos. Isso cria carga para o garbage collector e torna a execução mais lenta. Vamos verificar na prática:
\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); // Cada int é empacotado em object\n}\nsw.Stop();\nConsole.WriteLine($"Hashtable inserção: {sw.ElapsedMilliseconds} ms");\n\n// Dictionary: sem boxing\nDictionary<int, int> dict = new Dictionary<int, int>();\nsw.Restart();\nfor (int i = 0; i < 1000000; i++)\n{\n dict.Add(i, i); // Sem boxing — trabalhamos com int diretamente\n}\nsw.Stop();\nConsole.WriteLine($"Dictionary inserção: {sw.