C# Coleções

Uma coleção guarda vários valores juntos: a lista de compras, os preços da lojinha, a fila do banco. O .NET tem uma coleção para cada jeito de guardar. Esta página ajuda a escolher e depois lista os métodos de cada uma, com exemplo e resultado. Para aprender com calma, comece por Coleções e List<T>.

Os resultados vêm dos programas desta página (pasta ~/cs/col da Ana), rodados no dotnet 10.0.112 e no editor do guia, com a mesma saída. Todas estas coleções estão em System.Collections.Generic, que o C# já traz sem using.

Atalhos: Qual usar · Custo das operações · Criar e preencher · List · Dictionary · HashSet · Queue · Stack · Outras · Interfaces · Quando dá errado · Experimente


Qual usar

ColeçãoPense em…OrdemRepetidos?Acesso porUse quando…Aula
T[] (array)uma fileira de gavetas numeradas, de tamanho fixoa que você pôssimposiçãoo tamanho não muda (os 12 meses, as notas de 4 provas)Arrays
List<T>uma lista de compras que crescea que você pôssimposiçãoquase sempre: é a escolha padrãoList<T>
Dictionary<K, V>um dicionário: palavra → definiçãonão garantidachaves não; valores simchavevocê procura pela chave: código → produto, nome → telefoneDictionary
HashSet<T>a lista de convidados: cada nome uma veznão garantidanão"está aí?"tirar repetidos, perguntar se existe, união e interseçãoHashSet
Queue<T>a fila do banco: o primeiro que chega é o primeiro atendidode chegadasimsó o da frenteatender em ordem de chegada (pedidos, tarefas)Queue (Fila)
Stack<T>uma pilha de pratos: o último posto é o primeiro tiradoinversasimsó o de cimadesfazer, voltar, históricoStack (Pilha)
PriorityQueue<T, P>o pronto-socorro: o caso mais grave primeiropela prioridadesimsó o de menor prioridadeatender pela urgência .NET 6Queue (Fila)
SortedDictionary<K, V>um dicionário sempre em ordem alfabéticapela chavechaves nãochaveprecisa percorrer em ordem de chaveDictionary
SortedSet<T>um HashSet sempre em ordemcrescentenão"está aí?", mínimo, máximovalores únicos e ordenadosHashSet
LinkedList<T>uma corrente de elosa que você pôssimcomeço, fim, vizinhosmuitas inserções no meio, com o elo na mão (raro)LinkedList
Na dúvida, List

Comece com List<T>. Troque por Dictionary quando se pegar procurando itens por um código ou nome (lista.Find(p => p.Codigo == c) o tempo todo), e por HashSet quando só quer saber "já tem?".

Custo das operações

"Rápido" quer dizer que leva o mesmo tempo com 10 ou com 1 milhão de itens. "Proporcional" quer dizer que, com o dobro de itens, leva o dobro do tempo, porque a coleção olha item por item.

OperaçãoListDictionary / HashSetQueue / StackSortedDictionary / SortedSet
Acrescentarrápido (no fim)rápidorápidoquase rápido (cresce devagar com o tamanho)
Ler pela posiçãorápido—só a ponta—
Procurar um valorproporcionalrápido (pela chave)proporcionalquase rápido
Tirarproporcional (os de trás andam)rápidorápido (a ponta)quase rápido
Inserir no começo ou no meioproporcional———

Criar e preencher

FormaExemploDesde
Expressão de coleçãoList<string> compras = ["arroz", "feijão"]; · int[] v = [5, 3, 8]; · HashSet<int> s = [1, 2];C# 12
Coleção vaziaList<int> vazia = []; · Dictionary<string, int> d = [];C# 12
Espalhar (..)List<int> mais = [.. numeros, 10, 20]; → 5 3 8 1 10 20C# 12
new() com inicializadorList<string> c = new() { "arroz", "feijão" };C# 9 (o new()); C# 3 (o inicializador)
Dicionário com índicesDictionary<string, decimal> p = new() { ["caneta"] = 2.50m };C# 6
A forma antigavar c = new List<string>(); c.Add("arroz");C# 2
De uma consulta LINQList<string> nomes = produtos.Select(p => p.Nome).ToList();C# 3 (veja LINQ)

List<T>

// list.cs — List<T>
List<string> compras = ["arroz", "feijão"];
compras.Add("café");
compras.AddRange(["leite", "ovos"]);
compras.Insert(0, "pão");
Console.WriteLine($"1  {string.Join(", ", compras)} ({compras.Count})");
Console.WriteLine($"2  {compras[0]} {compras[^1]} {compras.IndexOf("café")} {compras.IndexOf("sal")} {compras.Contains("leite")}");
compras.Remove("feijão");
compras.RemoveAt(0);
Console.WriteLine($"3  {string.Join(", ", compras)}");
Console.WriteLine($"4  {compras.Find(c => c.StartsWith('l'))} {compras.FindIndex(c => c.Length == 4)} {compras.Exists(c => c == "sal")}");
compras.Sort();
Console.WriteLine($"5  {string.Join(", ", compras)}");
compras.Reverse();
Console.WriteLine($"6  {string.Join(", ", compras)}");
int tirados = compras.RemoveAll(c => c.Contains('o'));
Console.WriteLine($"7  tirei {tirados}: {string.Join(", ", compras)}");
compras[0] = "chá";
string[] array = compras.ToArray();
compras.Clear();
Console.WriteLine($"8  {array.Length} {array[0]} {compras.Count}");
List<int> numeros = [5, 3, 8, 1];
List<int> mais = [.. numeros, 10, 20];
Console.WriteLine($"9  {string.Join(" ", mais)} | {numeros.Sum()} {numeros.Max()} {numeros.Min()}");
$ dotnet run list.cs
1  pão, arroz, feijão, café, leite, ovos (6)
2  pão ovos 3 -1 True
3  arroz, café, leite, ovos
4  leite 1 False
5  arroz, café, leite, ovos
6  ovos, leite, café, arroz
7  tirei 2: leite, café
8  2 chá 0
9  5 3 8 1 10 20 | 17 8 1
MembroO que fazDevolveLinha da saída
Add(x)acrescenta no fimnada1
AddRange(itens)acrescenta vários no fimnada1
Insert(i, x)põe na posição i; os de trás andamnada1
Countquantos itens (propriedade)int1
[i], [^1]lê ou troca o item da posiçãoo item2, 8
IndexOf(x)a posição de x, ou -1int2
Contains(x)tem x?bool2
Remove(x)tira a primeira ocorrência de xbool (achou?)3
RemoveAt(i)tira o da posição inada3
Find(condição)o primeiro que cumpre, ou o padrão (null, 0)o item4
FindIndex(condição)a posição do primeiro que cumpre, ou -1int4
Exists(condição)algum cumpre?bool4
Sort()ordena a própria lista (texto em ordem de dicionário)nada5
Reverse()inverte a própria listanada6
RemoveAll(condição)tira todos os que cumpremint (quantos)7
ToArray()copia para um arrayT[]8
Clear()esvazianada8
Sum(), Max(), Min()do LINQ, funcionam em qualquer coleção (veja LINQ)o valor9

Aula: List<T>. Para ordenar por um campo (Sort((a, b) => a.Preco.CompareTo(b.Preco))), veja IComparable e IComparer.

Dictionary<TKey, TValue>

// dict.cs — Dictionary<TKey, TValue>
Dictionary<string, decimal> precos = new()
{
    ["caderno"] = 15.90m,
    ["caneta"] = 2.50m,
};
precos.Add("borracha", 1.75m);
precos["lápis"] = 1.20m;          // acrescenta
precos["caneta"] = 2.80m;         // troca o valor
Console.WriteLine($"1  {precos.Count} {precos["caneta"]:C} {precos.ContainsKey("régua")} {precos.ContainsValue(1.75m)}");
if (precos.TryGetValue("caderno", out decimal p))
{
    Console.WriteLine($"2  caderno: {p:C}");
}
Console.WriteLine($"3  {precos.GetValueOrDefault("régua")} {precos.GetValueOrDefault("régua", -1m)} {precos.TryAdd("caneta", 9m)} {precos.TryAdd("cola", 3.50m)}");
precos.Remove("borracha");
foreach (KeyValuePair<string, decimal> par in precos)
{
    Console.WriteLine($"4  {par.Key} = {par.Value:C}");
}
foreach (var (item, valor) in precos)
{
    Console.Write($"{item}:{valor} ");
}
Console.WriteLine();
Console.WriteLine($"5  {string.Join(", ", precos.Keys)} | {precos.Values.Sum():C}");
Dictionary<string, int> contagem = [];
foreach (string palavra in "a casa a rua a casa".Split(' '))
{
    contagem[palavra] = contagem.GetValueOrDefault(palavra) + 1;
}
Console.WriteLine($"6  {string.Join(" ", contagem.Select(kv => $"{kv.Key}={kv.Value}"))}");
$ dotnet run dict.cs
1  4 R$ 2,80 False True
2  caderno: R$ 15,90
3  0 -1 False True
4  caderno = R$ 15,90
4  caneta = R$ 2,80
4  lápis = R$ 1,20
4  cola = R$ 3,50
caderno:15,90 caneta:2,80 lápis:1,20 cola:3,50
5  caderno, caneta, lápis, cola | R$ 23,40
6  a=3 casa=2 rua=1
MembroO que fazSe a chave não existeSe a chave já existe
d[chave] (ler)o valor da chavelança KeyNotFoundExceptiondevolve o valor
d[chave] = valorguardaacrescentatroca o valor
Add(chave, valor)acrescentaacrescentalança ArgumentException
TryAdd(chave, valor)acrescenta se puderacrescenta e devolve Truenão mexe e devolve False
TryGetValue(chave, out v)o jeito seguro de lerdevolve Falsedevolve True e o valor em v
GetValueOrDefault(chave)lê, com um valor de reservadevolve o padrão (0) ou o que você passou (-1)devolve o valor
ContainsKey(chave)a chave existe?FalseTrue
ContainsValue(valor)algum valor é igual? (olha um por um: proporcional)True ou False
Remove(chave)tira o pardevolve Falsetira e devolve True
Count, Keys, Valuesquantos pares, só as chaves, só os valores—
foreach (var (k, v) in d)percorre os pares (KeyValuePair), desmontando em chave e valor—

A linha 6 é o "contador" clássico: contagem[palavra] = contagem.GetValueOrDefault(palavra) + 1; soma 1, começando do 0 quando a palavra é nova.

Não conte com a ordem do Dictionary

Na saída acima, os pares saíram na ordem em que entraram, mas isso não é garantido: depois de remoções, um item novo pode ocupar o lugar de um que saiu. Para mostrar em ordem, use OrderBy (d.OrderBy(kv => kv.Key)) ou um SortedDictionary. Aula: Dictionary.

HashSet<T>, Queue<T>, Stack<T> e as outras

// outros.cs — HashSet, Queue, Stack, PriorityQueue, SortedDictionary
HashSet<string> visitantes = ["Ana", "Bruno"];
Console.WriteLine($"1  {visitantes.Add("Carla")} {visitantes.Add("Ana")} {visitantes.Count} {visitantes.Contains("Bruno")}");
HashSet<int> a = [1, 2, 3, 4], b = [3, 4, 5];
HashSet<int> uniao = [.. a]; uniao.UnionWith(b);
HashSet<int> inter = [.. a]; inter.IntersectWith(b);
HashSet<int> dif = [.. a]; dif.ExceptWith(b);
Console.WriteLine($"2  {string.Join(",", uniao.Order())} | {string.Join(",", inter.Order())} | {string.Join(",", dif.Order())} | {a.IsSupersetOf([1, 2])}");

Queue<string> fila = new();
fila.Enqueue("Ana"); fila.Enqueue("Bruno"); fila.Enqueue("Carla");
Console.WriteLine($"3  {fila.Peek()} {fila.Dequeue()} {fila.Dequeue()} {fila.Count} {fila.TryDequeue(out string? prox)} {prox} {fila.TryDequeue(out _)}");

Stack<string> pilha = new();
pilha.Push("prato 1"); pilha.Push("prato 2"); pilha.Push("prato 3");
Console.WriteLine($"4  {pilha.Peek()} {pilha.Pop()} {pilha.Pop()} {pilha.Count} {string.Join("|", pilha)}");

PriorityQueue<string, int> atendimento = new();
atendimento.Enqueue("resfriado", 3);
atendimento.Enqueue("fratura", 1);
atendimento.Enqueue("dor de cabeça", 2);
Console.Write("5 ");
while (atendimento.TryDequeue(out string? caso, out int prioridade))
{
    Console.Write($" {caso}({prioridade})");
}
Console.WriteLine();

SortedDictionary<string, int> notas = new() { ["Carla"] = 9, ["Ana"] = 8, ["Bruno"] = 7 };
SortedSet<int> ordenados = [5, 1, 4, 1, 3];
Console.WriteLine($"6  {string.Join(" ", notas.Keys)} | {string.Join(" ", ordenados)} | {ordenados.Min} {ordenados.Max}");

int[] v = [5, 3, 8, 1];
Array.Sort(v);
Console.WriteLine($"7  {string.Join(" ", v)} {v.Length} {Array.IndexOf(v, 8)} {Array.BinarySearch(v, 5)} {v.Contains(3)}");
IReadOnlyList<int> soLeitura = v;
Console.WriteLine($"8  {soLeitura.Count} {soLeitura[0]}");
LinkedList<string> ll = new(["b", "c"]);
ll.AddFirst("a"); ll.AddLast("d");
Console.WriteLine($"9  {string.Join("", ll)} {ll.First!.Value} {ll.Last!.Value}");
$ dotnet run outros.cs
1  True False 3 True
2  1,2,3,4,5 | 3,4 | 1,2 | True
3  Ana Ana Bruno 1 True Carla False
4  prato 3 prato 3 prato 2 1 prato 1
5  fratura(1) dor de cabeça(2) resfriado(3)
6  Ana Bruno Carla | 1 3 4 5 | 1 5
7  1 3 5 8 4 3 2 True
8  4 1
9  abcd a d

HashSet<T> (linhas 1 e 2)

MembroO que fazExemplo → resultado
Add(x)acrescenta, se ainda não tem; devolve se acrescentouAdd("Carla") → True; Add("Ana") de novo → False
Contains(x)tem? (rápido, mesmo com milhões)Contains("Bruno") → True
Remove(x)tiradevolve se tirou
UnionWith(b)fica com tudo dos dois (união){1,2,3,4} ∪ {3,4,5} → 1,2,3,4,5
IntersectWith(b)fica só com o que está nos dois→ 3,4
ExceptWith(b)tira o que está em b→ 1,2
IsSubsetOf(b), IsSupersetOf(b), SetEquals(b)está contido? contém? são iguais?a.IsSupersetOf([1, 2]) → True

Os métodos …With mudam o próprio conjunto; por isso o programa copia antes ([.. a]). Para tirar repetidos de uma lista, basta new HashSet<T>(lista) ou lista.Distinct(). Aula: HashSet.

Queue<T>, a fila (linha 3)

MembroO que fazFila vazia
Enqueue(x)entra no fim da fila—
Dequeue()tira e devolve o da frentelança InvalidOperationException: Queue empty.
Peek()olha o da frente sem tirarlança a mesma exceção
TryDequeue(out x), TryPeek(out x)o jeito segurodevolve False
Countquantos esperam0

Aula: Queue (Fila).

Stack<T>, a pilha (linha 4)

MembroO que fazPilha vazia
Push(x)põe em cima—
Pop()tira e devolve o de cimalança InvalidOperationException: Stack empty.
Peek()olha o de cima sem tirarlança a mesma exceção
TryPop(out x), TryPeek(out x)o jeito segurodevolve False
foreachpercorre de cima para baixo—

Aula: Stack (Pilha).

As outras (linhas 5 a 9)

ColeçãoMembros principaisLinhaAula
PriorityQueue<T, P>Enqueue(item, prioridade), Dequeue(), TryDequeue(out item, out prioridade), Peek(). Sai primeiro a menor prioridade.5Queue (Fila)
SortedDictionary<K, V>os mesmos do Dictionary; o foreach e o Keys vêm em ordem de chave6Dictionary
SortedSet<T>os mesmos do HashSet, mais Min e Max; repetidos somem ([5, 1, 4, 1, 3] → 1 3 4 5)6HashSet
T[] e a classe ArrayLength, Array.Sort(v), Array.IndexOf(v, x), Array.BinarySearch(v, x) (só em array ordenado), Array.Reverse(v), Array.Resize(ref v, n)7A Classe Array
IReadOnlyList<T>só Count e [i]: uma "janela" que não deixa mudar8Encapsulamento
LinkedList<T>AddFirst, AddLast, AddBefore, AddAfter, First, Last, Remove9LinkedList

As interfaces: o que cada coleção sabe fazer

InterfacePrometeQuem temUse como parâmetro quando…
IEnumerable<T>dá para percorrer com foreach (e usar LINQ)todas, e as consultas LINQo método só vai ler, item por item
IReadOnlyCollection<T>+ Counttodasprecisa saber quantos
IReadOnlyList<T>+ [i] para lerarray, Listprecisa ler por posição, sem mudar
ICollection<T>Add, Remove, Contains, CountList, HashSet, LinkedList…vai acrescentar ou tirar
IList<T>+ [i], Insert, RemoveAtarray, Listvai mudar por posição
IDictionary<K, V> / IReadOnlyDictionary<K, V>acesso por chaveDictionary, SortedDictionarytrabalha com pares

Pedir a interface mais simples que serve deixa o método aceitar mais tipos: um Listar(IEnumerable<Produto> produtos) aceita uma List, um array e o resultado de um Where (o projeto Estoque faz isso). Aula: IEnumerable e foreach.


Quando dá errado

CódigoMensagem real (lab)O que significa e como consertarAula
precos["régua"] (chave que não existe)System.Collections.Generic.KeyNotFoundException: The given key 'régua' was not present in the dictionary."A chave não está no dicionário". Use TryGetValue ou GetValueOrDefault, ou confira com ContainsKey.Dictionary
precos.Add("caneta", 2.80m) (chave repetida)System.ArgumentException: An item with the same key has already been added. Key: caneta"Já existe um item com essa chave". Para trocar o valor, precos["caneta"] = 2.80m; para só acrescentar se não houver, TryAdd.Dictionary
compras[2] numa lista de 2 itensSystem.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')As posições vão de 0 a Count - 1. (Num array, a exceção é outra: IndexOutOfRangeException.)List<T>
fila.Dequeue() com a fila vaziaSystem.InvalidOperationException: Queue empty.Confira Count > 0 ou use TryDequeue.Queue
pilha.Pop() com a pilha vaziaSystem.InvalidOperationException: Stack empty.Confira Count > 0 ou use TryPop.Stack
vazia.First()System.InvalidOperationException: Sequence contains no elementsO First do LINQ numa coleção vazia. Use FirstOrDefault.LINQ
Remove/Add dentro do foreach da mesma listaSystem.InvalidOperationException: Collection was modified; enumeration operation may not execute.Use RemoveAll(condição), ou percorra uma cópia (foreach (var x in lista.ToList())). Exemplo completo no projeto Estoque.foreach
numeros.Add("4") numa List<int>error CS1503: Argumento 1: não é possível converter de "string" para "int"A lista só aceita o tipo dela. Converta (int.Parse("4")) ou ponha 4.Generics
var vazia = [];error CS9176: Não há um tipo de destino para a expressão de coleção.Os colchetes não dizem o tipo. Escreva o tipo à esquerda: List<int> vazia = [];List<T>
numeros.Length numa Listerror CS1061: ‘List<int>’ não contém uma definição para "Length" …Na List, é Count. Length é do array e da string.List<T>
Console.WriteLine(v.Count) num arrayerror CS1503: Argumento 1: não é possível converter de "grupo de métodos" para "bool"O array não tem a propriedade Count; o compilador achou o método Count() do LINQ, sem os parênteses, e se confundiu. Use v.Length (ou v.Count()).Arrays

No editor do guia, o rastro de algumas destas exceções mostra os tipos de verdade no lugar dos genéricos (Dictionary`2.get_Item(String key) em vez de get_Item(TKey key)); a mensagem é a mesma.

Experimente

Missão: a Ana quer contar quantas vezes cada fruta foi vendida, mas o programa cai com KeyNotFoundException na primeira fruta. Conserte a linha do contagem[fruta]++ para começar do zero quando a fruta é nova. A saída deve ter banana=3, maçã=2 e uva=1.

string[] vendas = ["banana", "maçã", "banana", "uva", "banana", "maçã"];
Dictionary<string, int> contagem = [];
foreach (string fruta in vendas)
{
    contagem[fruta]++;
}
foreach (var (fruta, vezes) in contagem)
{
    Console.WriteLine($"{fruta}={vezes}");
}
string[] vendas = ["banana", "maçã", "banana", "uva", "banana", "maçã"];
Dictionary<string, int> contagem = [];
foreach (string fruta in vendas)
{
    contagem[fruta] = contagem.GetValueOrDefault(fruta) + 1;
}
foreach (var (fruta, vezes) in contagem)
{
    Console.WriteLine($"{fruta}={vezes}");
}

Resumo

  • Padrão: List<T>. Por chave: Dictionary<K, V>. Sem repetidos: HashSet<T>. Ordem de chegada: Queue<T>. Último primeiro: Stack<T>. Tamanho fixo: array.
  • List tem Count; array e string têm Length.
  • No Dictionary, ler uma chave que não existe lança exceção: use TryGetValue ou GetValueOrDefault. Add com chave repetida também lança; d[k] = v troca.
  • Dequeue, Pop e Peek numa coleção vazia lançam InvalidOperationException: use as versões Try….
  • Não conte com a ordem de Dictionary e HashSet; para ordem, Sorted… ou OrderBy.
  • C# 12: [1, 2, 3], [] e [.. outra, 4] criam qualquer coleção.