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ção | Pense em… | Ordem | Repetidos? | Acesso por | Use quando… | Aula |
|---|---|---|---|---|---|---|
T[] (array) | uma fileira de gavetas numeradas, de tamanho fixo | a que você pôs | sim | posição | o tamanho não muda (os 12 meses, as notas de 4 provas) | Arrays |
List<T> | uma lista de compras que cresce | a que você pôs | sim | posição | quase sempre: é a escolha padrão | List<T> |
Dictionary<K, V> | um dicionário: palavra → definição | não garantida | chaves não; valores sim | chave | você procura pela chave: código → produto, nome → telefone | Dictionary |
HashSet<T> | a lista de convidados: cada nome uma vez | não garantida | não | "está aí?" | tirar repetidos, perguntar se existe, união e interseção | HashSet |
Queue<T> | a fila do banco: o primeiro que chega é o primeiro atendido | de chegada | sim | só o da frente | atender em ordem de chegada (pedidos, tarefas) | Queue (Fila) |
Stack<T> | uma pilha de pratos: o último posto é o primeiro tirado | inversa | sim | só o de cima | desfazer, voltar, histórico | Stack (Pilha) |
PriorityQueue<T, P> | o pronto-socorro: o caso mais grave primeiro | pela prioridade | sim | só o de menor prioridade | atender pela urgência .NET 6 | Queue (Fila) |
SortedDictionary<K, V> | um dicionário sempre em ordem alfabética | pela chave | chaves não | chave | precisa percorrer em ordem de chave | Dictionary |
SortedSet<T> | um HashSet sempre em ordem | crescente | não | "está aí?", mínimo, máximo | valores únicos e ordenados | HashSet |
LinkedList<T> | uma corrente de elos | a que você pôs | sim | começo, fim, vizinhos | muitas inserções no meio, com o elo na mão (raro) | LinkedList |
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ção | List | Dictionary / HashSet | Queue / Stack | SortedDictionary / SortedSet |
|---|---|---|---|---|
| Acrescentar | rápido (no fim) | rápido | rápido | quase rápido (cresce devagar com o tamanho) |
| Ler pela posição | rápido | — | só a ponta | — |
| Procurar um valor | proporcional | rápido (pela chave) | proporcional | quase rápido |
| Tirar | proporcional (os de trás andam) | rápido | rápido (a ponta) | quase rápido |
| Inserir no começo ou no meio | proporcional | — | — | — |
Criar e preencher
| Forma | Exemplo | Desde |
|---|---|---|
| Expressão de coleção | List<string> compras = ["arroz", "feijão"]; · int[] v = [5, 3, 8]; · HashSet<int> s = [1, 2]; | C# 12 |
| Coleção vazia | List<int> vazia = []; · Dictionary<string, int> d = []; | C# 12 |
Espalhar (..) | List<int> mais = [.. numeros, 10, 20]; → 5 3 8 1 10 20 | C# 12 |
new() com inicializador | List<string> c = new() { "arroz", "feijão" }; | C# 9 (o new()); C# 3 (o inicializador) |
| Dicionário com índices | Dictionary<string, decimal> p = new() { ["caneta"] = 2.50m }; | C# 6 |
| A forma antiga | var c = new List<string>(); c.Add("arroz"); | C# 2 |
| De uma consulta LINQ | List<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
| Membro | O que faz | Devolve | Linha da saída |
|---|---|---|---|
Add(x) | acrescenta no fim | nada | 1 |
AddRange(itens) | acrescenta vários no fim | nada | 1 |
Insert(i, x) | põe na posição i; os de trás andam | nada | 1 |
Count | quantos itens (propriedade) | int | 1 |
[i], [^1] | lê ou troca o item da posição | o item | 2, 8 |
IndexOf(x) | a posição de x, ou -1 | int | 2 |
Contains(x) | tem x? | bool | 2 |
Remove(x) | tira a primeira ocorrência de x | bool (achou?) | 3 |
RemoveAt(i) | tira o da posição i | nada | 3 |
Find(condição) | o primeiro que cumpre, ou o padrão (null, 0) | o item | 4 |
FindIndex(condição) | a posição do primeiro que cumpre, ou -1 | int | 4 |
Exists(condição) | algum cumpre? | bool | 4 |
Sort() | ordena a própria lista (texto em ordem de dicionário) | nada | 5 |
Reverse() | inverte a própria lista | nada | 6 |
RemoveAll(condição) | tira todos os que cumprem | int (quantos) | 7 |
ToArray() | copia para um array | T[] | 8 |
Clear() | esvazia | nada | 8 |
Sum(), Max(), Min() | do LINQ, funcionam em qualquer coleção (veja LINQ) | o valor | 9 |
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
| Membro | O que faz | Se a chave não existe | Se a chave já existe |
|---|---|---|---|
d[chave] (ler) | o valor da chave | lança KeyNotFoundException | devolve o valor |
d[chave] = valor | guarda | acrescenta | troca o valor |
Add(chave, valor) | acrescenta | acrescenta | lança ArgumentException |
TryAdd(chave, valor) | acrescenta se puder | acrescenta e devolve True | não mexe e devolve False |
TryGetValue(chave, out v) | o jeito seguro de ler | devolve False | devolve True e o valor em v |
GetValueOrDefault(chave) | lê, com um valor de reserva | devolve o padrão (0) ou o que você passou (-1) | devolve o valor |
ContainsKey(chave) | a chave existe? | False | True |
ContainsValue(valor) | algum valor é igual? (olha um por um: proporcional) | True ou False | |
Remove(chave) | tira o par | devolve False | tira e devolve True |
Count, Keys, Values | quantos 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.
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)
| Membro | O que faz | Exemplo → resultado |
|---|---|---|
Add(x) | acrescenta, se ainda não tem; devolve se acrescentou | Add("Carla") → True; Add("Ana") de novo → False |
Contains(x) | tem? (rápido, mesmo com milhões) | Contains("Bruno") → True |
Remove(x) | tira | devolve 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)
| Membro | O que faz | Fila vazia |
|---|---|---|
Enqueue(x) | entra no fim da fila | — |
Dequeue() | tira e devolve o da frente | lança InvalidOperationException: Queue empty. |
Peek() | olha o da frente sem tirar | lança a mesma exceção |
TryDequeue(out x), TryPeek(out x) | o jeito seguro | devolve False |
Count | quantos esperam | 0 |
Aula: Queue (Fila).
Stack<T>, a pilha (linha 4)
| Membro | O que faz | Pilha vazia |
|---|---|---|
Push(x) | põe em cima | — |
Pop() | tira e devolve o de cima | lança InvalidOperationException: Stack empty. |
Peek() | olha o de cima sem tirar | lança a mesma exceção |
TryPop(out x), TryPeek(out x) | o jeito seguro | devolve False |
foreach | percorre de cima para baixo | — |
Aula: Stack (Pilha).
As outras (linhas 5 a 9)
| Coleção | Membros principais | Linha | Aula |
|---|---|---|---|
PriorityQueue<T, P> | Enqueue(item, prioridade), Dequeue(), TryDequeue(out item, out prioridade), Peek(). Sai primeiro a menor prioridade. | 5 | Queue (Fila) |
SortedDictionary<K, V> | os mesmos do Dictionary; o foreach e o Keys vêm em ordem de chave | 6 | Dictionary |
SortedSet<T> | os mesmos do HashSet, mais Min e Max; repetidos somem ([5, 1, 4, 1, 3] → 1 3 4 5) | 6 | HashSet |
T[] e a classe Array | Length, Array.Sort(v), Array.IndexOf(v, x), Array.BinarySearch(v, x) (só em array ordenado), Array.Reverse(v), Array.Resize(ref v, n) | 7 | A Classe Array |
IReadOnlyList<T> | só Count e [i]: uma "janela" que não deixa mudar | 8 | Encapsulamento |
LinkedList<T> | AddFirst, AddLast, AddBefore, AddAfter, First, Last, Remove | 9 | LinkedList |
As interfaces: o que cada coleção sabe fazer
| Interface | Promete | Quem tem | Use como parâmetro quando… |
|---|---|---|---|
IEnumerable<T> | dá para percorrer com foreach (e usar LINQ) | todas, e as consultas LINQ | o método só vai ler, item por item |
IReadOnlyCollection<T> | + Count | todas | precisa saber quantos |
IReadOnlyList<T> | + [i] para ler | array, List | precisa ler por posição, sem mudar |
ICollection<T> | Add, Remove, Contains, Count | List, HashSet, LinkedList… | vai acrescentar ou tirar |
IList<T> | + [i], Insert, RemoveAt | array, List | vai mudar por posição |
IDictionary<K, V> / IReadOnlyDictionary<K, V> | acesso por chave | Dictionary, SortedDictionary | trabalha 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ódigo | Mensagem real (lab) | O que significa e como consertar | Aula |
|---|---|---|---|
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 itens | System.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 vazia | System.InvalidOperationException: Queue empty. | Confira Count > 0 ou use TryDequeue. | Queue |
pilha.Pop() com a pilha vazia | System.InvalidOperationException: Stack empty. | Confira Count > 0 ou use TryPop. | Stack |
vazia.First() | System.InvalidOperationException: Sequence contains no elements | O First do LINQ numa coleção vazia. Use FirstOrDefault. | LINQ |
Remove/Add dentro do foreach da mesma lista | System.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 List | error 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 array | error 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. ListtemCount; array e string têmLength.- No
Dictionary, ler uma chave que não existe lança exceção: useTryGetValueouGetValueOrDefault.Addcom chave repetida também lança;d[k] = vtroca. Dequeue,PopePeeknuma coleção vazia lançamInvalidOperationException: use as versõesTry….- Não conte com a ordem de
DictionaryeHashSet; para ordem,Sorted…ouOrderBy. - C# 12:
[1, 2, 3],[]e[.. outra, 4]criam qualquer coleção.