
C# para Iniciantes: Coleções no .NET (arrays, List, Dictionary, HashSet, imutáveis e “frozen”)
Palavra-chave foco: coleções em C# para iniciantes (listas, dicionários, conjuntos, imutáveis)
Entender coleções é essencial para escrever código limpo e performático. Neste guia prático (baseado no Cap. 7), você vai aprender quando usar cada coleção, como escolher comparadores, e quando preferir imutabilidade ou estruturas otimizadas para leitura.
1) Panorama rápido (quando usar o quê)
- Array (
T[]) – tamanho fixo e acesso por índice O(1). Ideal para buffers e cenários de alto desempenho. List<T>– lista dinâmica, ordenada, mais usada no dia a dia. Inserções no fim são amortizadas O(1).Dictionary<TKey,TValue>– mapa chave→valor com busca O(1) em média.HashSet<T>– conjunto sem duplicatas, pertinência O(1) em média.SortedDictionary/SortedSet– mantêm ordenação por comparador (árvore balanceada, operações O(log n)).Queue<T>/Stack<T>– FIFO e LIFO, respectivamente, O(1) para enfileirar/empilhar.- Coleções Imutáveis (
System.Collections.Immutable) – variam por operações seguras e previsíveis em concorrência. - Coleções “Frozen” (
FrozenDictionary/ FrozenSet) – otimizadas para leitura super rápida após construção (ótimas para tabelas estáticas).
2) List<T>: o canivete suíço
var numeros = new List<int> { 3, 1, 4 };
numeros.Add(1);
numeros.Sort(); // ordena in-place
bool temQuatro = numeros.Contains(4);
Boas práticas
- Prefira
AddRangeao inserir muitos itens. - Use
Capacityquando souber o tamanho (evita realocações). - Para leitura somente, exponha
IReadOnlyList<T>.
3) Dicionários e conjuntos (hash)
var porId = new Dictionary<Guid, string>();
var id = Guid.NewGuid();
porId[id] = "Ana";
Console.WriteLine(porId.TryGetValue(id, out var nome) ? nome : "N/D");
var tags = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ "csharp", "dotnet" };
tags.Add("CSharp"); // não duplica por causa do comparador
Dicas
- Escolha um
IEqualityComparer<T>adequado (ex.:StringComparer.OrdinalIgnoreCase). HashSet<T>é perfeito para “já vi / não vi”.- Para cenários com muitas leituras e coleção estática, considere Frozen.
4) Coleções ordenadas (comparadores)
var porNome = new SortedSet<string>(StringComparer.InvariantCultureIgnoreCase)
{ "Zebra", "ábaco", "Ana" };
foreach (var s in porNome) Console.WriteLine(s); // já sai ordenado
var porChave = new SortedDictionary<int, string>
{
[10] = "dez", [2] = "dois", [7] = "sete"
};
- Quando usar: quando ordem natural importa e você quer busca por intervalo, min/max eficientes, ou iteração já ordenada.
- Forneça
IComparer<T>para controlar a ordenação (por cultura, case, etc.).
5) Imutáveis e “Frozen”: leitura estável e rápida
using System.Collections.Immutable;
var imutavel = ImmutableList.Create(1, 2, 3);
var nova = imutavel.Add(4); // cria uma nova versão, a antiga continua válida
- Imutáveis: ótimas em pipelines de concorrência e para estado previsível (evitam bugs por alteração acidental).
- Frozen: construa uma vez, depois consultas rápidas e sem alocações. Útil para tabelas de lookup estáticas (p.ex. códigos de país → idioma).
6) Comparadores e regras de igualdade (chave para corretude)
- Use
IEqualityComparer<T>emDictionary/HashSetpara definir igualdade (ex.: ignorar acentos/case). - Use
IComparer<T>emSorted*para definir ordem. - Tipos de domínio podem implementar
IEquatable<T>para performance e semântica consistente.
sealed class ProdutoIdComparer : IEqualityComparer<ProdutoId>
{
public bool Equals(ProdutoId x, ProdutoId y) => x.Value == y.Value;
public int GetHashCode(ProdutoId obj) => obj.Value.GetHashCode();
}
7) Escolha rápida (checklist)
- Precisa índice rápido e tamanho fixo? →
T[] - Lista que cresce e ordenação eventual? →
List<T>+Sort - Busca por chave (rápida) / presença? →
Dictionary/HashSet - Iterar já ordenado / intervalos? →
SortedDictionary/SortedSet - Concorrência / estado previsível? → Imutáveis
- Lookup estático e hot path de leitura? → Frozen
- Strings como chave? → compare com
StringComparer.Ordinal(ouOrdinalIgnoreCasese fizer sentido)
8) Performance e armadilhas comuns
- Evite chamar
Contains/Removerepetidamente emList<T>para grandes volumes — considereHashSet<T>. - Inicialize capacidade (
new List<T>(cap),new Dictionary<K,V>(cap)) quando souber o tamanho esperado. - Cuidado com cultura: para chaves string, Ordinal/OrdinalIgnoreCase é geralmente mais rápido e previsível.
- Não exponha listas mutáveis: prefira interfaces somente leitura na API pública.
- Streaming? Combine coleções com LINQ (execução em demanda) e iteradores (
yield return).
9) Exemplos práticos
Remover duplicatas de maneira rápida
var dedup = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var listaLimpa = lista.Where(dedup.Add).ToList();
Índice por chave derivada (lookup)
var porDominio = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
foreach (var email in emails)
{
var dominio = email.Split('@')[1];
porDominio.TryAdd(dominio, new List<string>());
porDominio[dominio].Add(email);
}
Tabela estática para leitura “hot”
// Supondo .NET 7+ com coleções frozen disponíveis
using System.Collections.Frozen;
var regioes = new Dictionary<string, string>(StringComparer.Ordinal)
{
["BR"] = "Brasil",
["US"] = "United States",
["JP"] = "Japan"
}.ToFrozenDictionary();
// Consultas muito rápidas:
var nome = regioes.TryGetValue("BR", out var val) ? val : "N/D";
FAQ (para SEO)
List<T> vs T[]?T[] é fixo e levemente mais rápido em acesso bruto; List<T> cresce dinamicamente e oferece métodos utilitários (Add, Sort, Find).
Dictionary vs SortedDictionary?Dictionary é mais rápido para busca direta (hash). SortedDictionary mantém ordem e permite operações por intervalo, com custo O(log n).
Quando usar imutáveis?
Quando você quer segurança em concorrência, histórico de versões, e APIs previsíveis (nenhum consumidor altera seu estado sem perceber).
Preciso sempre de comparador customizado?
Não. Mas para strings, prefira StringComparer.Ordinal (ou OrdinalIgnoreCase) a menos que você queira colação por cultura.
Post baseado no Capítulo 7 — “Collections” do livro C# 12 in a Nutshell, de Joseph Albahari (O’Reilly).