0
Promoção de Volta das Aulas ! Cursos com 25% OFF no menu "Cursos"
outubro 10, 2025
0
César Fontanella

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 AddRange ao inserir muitos itens.
  • Use Capacity quando 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> em Dictionary/HashSet para definir igualdade (ex.: ignorar acentos/case).
  • Use IComparer<T> em Sorted* 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 (ou OrdinalIgnoreCase se fizer sentido)

8) Performance e armadilhas comuns

  • Evite chamar Contains/Remove repetidamente em List<T> para grandes volumes — considere HashSet<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).