C# Coleções
Uma coleção é um objeto que guarda vários valores: a lista de compras, os preços da lojinha, a fila do caixa. Você já conhece uma, o array. O .NET tem várias outras, cada uma boa para um tipo de trabalho. Esta aula é o mapa: você vai ver por que o array não basta, conhecer as principais coleções num exemplo curto de cada e aprender a escolher. Cada uma tem aula própria, logo em seguida.
- Por que o array tem tamanho fixo, e o que isso atrapalha.
- O que quer dizer o
<T>deList<T>. - Para que serve cada coleção:
List,Dictionary,HashSet,Queue,StackeLinkedList. - O que todas têm em comum:
Count,Add,Contains,foreach, as expressões de coleção[ … ]. - Como escolher a coleção certa.
- Os erros
CS1061,CS0305,CS1503e a chave repetida no dicionário.
Todos os programas rodaram na máquina da Ana, com o .NET 10 (C# 14), usando dotnet run arquivo.cs na pasta ~/cs.
A ideia antes do código
Pense em como você guarda coisas em casa. Os ovos vão numa caixa de ovos: 12 buracos, nem mais nem menos. A lista de compras vai num papel, onde dá para acrescentar e riscar itens. Os telefones dos amigos vão numa agenda, onde você procura pelo nome. Na padaria, a fila é por ordem de chegada; na pia, a pilha de pratos se lava de cima para baixo.
Cada forma de guardar combina com um jeito de usar. Em C# é igual:
| No dia a dia | Em C# | O que ela faz bem |
|---|---|---|
| Caixa de ovos (tamanho fixo) | array T[] | Guardar uma quantidade conhecida, por posição |
| Lista de compras | List<T> | Guardar em ordem, acrescentar e tirar |
| Agenda (nome → telefone) | Dictionary<TKey, TValue> | Achar um valor pela chave, rápido |
| Lista de presença (cada aluno uma vez) | HashSet<T> | Guardar sem repetidos; saber se algo está lá |
| Fila da padaria | Queue<T> | Primeiro a entrar, primeiro a sair |
| Pilha de pratos | Stack<T> | Último a entrar, primeiro a sair |
| Trem de vagões engatados | LinkedList<T> | Encaixar e soltar no meio sem empurrar os outros |
1. O limite do array
Um array nasce com um tamanho e nunca muda. A Ana quis acrescentar o leite à lista:
string[] compras = ["arroz", "feijão", "café"];
compras.Add("leite");
$ dotnet run array.cs /home/ana/cs/array.cs(2,9): error CS1061: ‘string[]’ não contém uma definição para "Add" e não foi possível encontrar nenhum método de extensão "Add" que aceite um primeiro argumento do tipo ‘string[]’ (você está se esquecendo de usar uma diretiva ou uma referência de assembly?) Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
"string[] não contém uma definição para Add": arrays não têm "acrescentar", porque não têm onde pôr mais um. Dá para criar um array maior e copiar tudo (é o que Array.Resize faz, aula A Classe Array), mas fazer isso a cada item é trabalhoso e lento.
Com uma List<string>, a mesma ideia funciona:
List<string> compras = ["arroz", "feijão", "café"];
compras.Add("leite");
compras.Remove("feijão");
Console.WriteLine($"{compras.Count} itens: {string.Join(", ", compras)}");
$ dotnet run lista.cs 3 itens: arroz, café, leite
- List<string>
- "Lista de textos". A lista começa com três itens e cresce ou encolhe conforme a necessidade.
- ["arroz", "feijão", "café"]
- Uma expressão de coleção, a mesma que você usou nos arrays. Funciona com quase todas as coleções C# 12.
- compras.Add("leite")
- Acrescenta no fim.
- compras.Remove("feijão")
- Tira o item, e os de depois "andam" uma casa.
- compras.Count
- Quantos itens há agora: 3. Nas coleções é
Count; nos arrays éLength. - string.Join(", ", compras)
- Junta os itens num texto, separados por vírgula (aula Métodos de String).
Missão: o programa não compila, porque um array não tem Add nem Count. Troque só o tipo da variável, de string[] para List<string>, e rode.
string[] compras = ["arroz", "feijão", "café"];
compras.Add("leite");
Console.WriteLine($"{compras.Count} itens: {string.Join(", ", compras)}");
List<string> compras = ["arroz", "feijão", "café"];
compras.Add("leite");
Console.WriteLine($"{compras.Count} itens: {string.Join(", ", compras)}");
2. O <T>: coleções de um tipo só
O nome oficial é List<T>, e o T (de type, "tipo") é um espaço para você dizer o que a lista guarda: List<string>, List<int>, List<decimal>, List<Produto>. Classes com esse espaço se chamam genéricas (aula Generics). O Dictionary<TKey, TValue> tem dois espaços: o tipo da chave e o tipo do valor.
A vantagem: o compilador sabe o que está lá dentro, e não deixa entrar outra coisa.
List<int> notas = [7, 9, 6];
notas.Add("dez");
$ dotnet run erros3.cs /home/ana/cs/erros3.cs(2,11): error CS1503: Argumento 1: não é possível converter de "string" para "int" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O erro aparece antes de rodar: uma lista de int não aceita texto. Compare com a forma antiga, do tempo em que o C# não tinha genéricos (C# 1, 2002). A ArrayList guarda object, isto é, qualquer coisa:
using System.Collections;
ArrayList coisas = new ArrayList();
coisas.Add("arroz");
coisas.Add(42);
coisas.Add(3.5);
foreach (object c in coisas)
{
Console.WriteLine($"{c} ({c.GetType().Name})");
}
int total = 0;
foreach (object c in coisas)
{
total += (int)c;
}
$ dotnet run antiga.cs arroz (String) 42 (Int32) 3,5 (Double) Unhandled exception. System.InvalidCastException: Unable to cast object of type 'System.String' to type 'System.Int32'. at Program.<Main>$(String[] args) in /home/ana/cs/antiga.cs:line 14
Compilou sem reclamar e quebrou ao rodar, porque um texto não vira int (a InvalidCastException da aula Exceções). Com List<int>, esse defeito nem chegaria a compilar. As coleções antigas (ArrayList, Hashtable, do namespace System.Collections) ainda existem por compatibilidade; em código novo, use sempre as genéricas, de System.Collections.Generic, que já vem nos usings implícitos.
3. As principais coleções, uma de cada
Um programa com cada coleção fazendo aquilo em que é boa. Os detalhes de cada uma estão nas aulas próprias.
// List<T>: em ordem, cresce e encolhe
List<string> compras = ["arroz", "feijão"];
compras.Add("café");
Console.WriteLine($"List: {string.Join(", ", compras)} (o 1º é {compras[0]})");
// Dictionary<TKey, TValue>: procura pela chave
Dictionary<string, decimal> precos = new()
{
["arroz"] = 6.50m,
["café"] = 18.90m
};
Console.WriteLine($"Dictionary: o café custa {precos["café"]:C}");
// HashSet<T>: sem repetidos
HashSet<string> visitantes = ["Ana", "Bia", "Ana", "Caio", "Bia"];
Console.WriteLine($"HashSet: {visitantes.Count} pessoas diferentes");
// Queue<T>: fila, o primeiro a entrar é o primeiro a sair
Queue<string> fila = new();
fila.Enqueue("Ana");
fila.Enqueue("Bia");
fila.Enqueue("Caio");
Console.WriteLine($"Queue: atendendo {fila.Dequeue()}, restam {fila.Count}");
// Stack<T>: pilha, o último a entrar é o primeiro a sair
Stack<string> pratos = new();
pratos.Push("prato 1");
pratos.Push("prato 2");
pratos.Push("prato 3");
Console.WriteLine($"Stack: lavando o {pratos.Pop()}, restam {pratos.Count}");
$ dotnet run tour.cs List: arroz, feijão, café (o 1º é arroz) Dictionary: o café custa R$ 18,90 HashSet: 3 pessoas diferentes Queue: atendendo Ana, restam 2 Stack: lavando o prato 3, restam 2
- List<T> ·
compras[0] - Os itens ficam na ordem em que entraram, e cada um tem uma posição, como no array. Aula: List<T>.
- Dictionary<string, decimal> ·
precos["café"] - Cada chave (
"café") leva a um valor (18.90m). Achar pela chave é muito rápido, mesmo com milhões de itens. Onew()sem o tipo repetido é onew"pelo tipo de destino", e["arroz"] = 6.50mpõe um par chave → valor. Aula: Dictionary. - HashSet<string> · 3 pessoas
- Entraram cinco nomes, mas os repetidos foram ignorados:
Ana,BiaeCaio. Aula: HashSet<T>. - Queue<string> · Enqueue / Dequeue
- Enqueue = "entrar na fila"; Dequeue = "sair da fila", e devolve quem saiu. Saiu a
Ana, que chegou primeiro. Aula: Queue<T>. - Stack<string> · Push / Pop
- Push = "empilhar"; Pop = "desempilhar" o de cima. Saiu o
prato 3, o último que entrou. Aula: Stack<T>.
A LinkedList<T> (lista ligada) não está no programa porque é bem menos usada: ela só ganha da List quando o programa insere e remove muitos itens no meio. A aula LinkedList<T> mostra quando.
HashSet e o Dictionary não prometem ordem
A List, a Queue e a Stack mantêm a ordem de entrada. O HashSet e o Dictionary organizam os itens do jeito que for mais rápido para procurar, e a ordem em que o foreach os mostra pode mudar depois de remoções. Se a ordem importa, use uma List ou ordene na hora de mostrar (aula Ordenar).
Missão: a Ana quer saber quantas pessoas diferentes visitaram a loja, mas a List conta as repetidas e mostra 5. Troque o tipo da coleção por um que ignore repetidos.
List<string> visitantes = ["Ana", "Bia", "Ana", "Caio", "Bia"];
Console.WriteLine($"{visitantes.Count} pessoas diferentes");
HashSet<string> visitantes = ["Ana", "Bia", "Ana", "Caio", "Bia"];
Console.WriteLine($"{visitantes.Count} pessoas diferentes");
4. O que todas têm em comum
As coleções são diferentes por dentro, mas compartilham várias operações, e o foreach funciona com todas (e com arrays):
List<string> compras = ["arroz", "feijão", "café"];
HashSet<int> numeros = [3, 1, 2];
Queue<string> fila = new(["Ana", "Bia"]);
int[] notas = [7, 9, 6];
Console.WriteLine($"{compras.Count} {numeros.Count} {fila.Count} {notas.Length}");
Console.WriteLine(compras.Contains("café"));
Console.WriteLine(numeros.Contains(5));
foreach (string item in compras)
{
Console.WriteLine($"- {item}");
}
compras.Clear();
Console.WriteLine(compras.Count);
$ dotnet run comum.cs 3 3 2 3 True False - arroz - feijão - café 0
| Operação | O que faz | Onde existe |
|---|---|---|
.Count | Quantos itens há | Todas as coleções (no array: .Length) |
.Contains(x) | true se x está lá | List, HashSet, Queue, Stack, LinkedList (no Dictionary: ContainsKey) |
.Add(x) | Acrescenta | List, HashSet, Dictionary (Add(chave, valor)); na fila e na pilha o nome é outro (Enqueue, Push) |
.Clear() | Esvazia | Todas as coleções |
foreach (var x in c) | Passa por todos os itens | Todas, e arrays (aula IEnumerable e foreach) |
[a, b, c] | Cria já com itens C# 12 | List, HashSet, arrays e outras (não Dictionary, Queue, Stack nem LinkedList, que não têm Add com um argumento) |
new T(outraColecao) | Cria copiando outra coleção | Todas, como a Queue do exemplo |
E todas funcionam com o LINQ (Where, Select, Sum, OrderBy…), que você vai conhecer no fim deste nível.
Qual coleção usar
| Você precisa… | Use |
|---|---|
| de uma quantidade fixa, conhecida desde o começo (os 12 meses, as 5 notas) | array T[] |
| de uma lista em ordem, que cresce e encolhe (a escolha padrão) | List<T> |
| achar algo por um nome, código ou CPF | Dictionary<TKey, TValue> |
| garantir que não há repetidos, ou perguntar "está lá?" muitas vezes | HashSet<T> |
| atender por ordem de chegada | Queue<T> |
| desfazer a última ação, voltar ao anterior | Stack<T> |
| inserir e remover muito no meio de uma lista enorme | LinkedList<T> |
| na dúvida | List<T>: troque depois, se precisar |
A tabela completa, com o custo de cada operação, está em Referência: Coleções.
Quando dá errado
Length numa lista (ou Add num array)
List<int> notas = [7, 9, 6]; Console.WriteLine(notas.Length);
$ dotnet run erros1.cs /home/ana/cs/erros1.cs(2,25): error CS1061: ‘List<int>’ não contém uma definição para "Length" e não foi possível encontrar nenhum método de extensão "Length" que aceite um primeiro argumento do tipo ‘List<int>’ (você está se esquecendo de usar uma diretiva ou uma referência de assembly?) Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: "o tipo não contém uma definição para Length". Array usa Length; coleções usam Count. É a confusão mais comum de quem passa de arrays para listas.
Como consertar: notas.Count. No caso contrário (Add num array), troque o array por uma List.
List sem o tipo
List compras = new List();
$ dotnet run erros2.cs /home/ana/cs/erros2.cs(1,1): error CS0305: Usar o genérico tipo "List<T>" requer 1 argumentos de tipo /home/ana/cs/erros2.cs(1,20): error CS0305: Usar o genérico tipo "List<T>" requer 1 argumentos de tipo Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: List<T> precisa de um tipo no lugar do T ("requer 1 argumento de tipo"). O erro aparece duas vezes porque List aparece duas vezes na linha.
Como consertar: diga o que a lista guarda: List<string> compras = new(); ou List<string> compras = [];.
O que significa: você tentou pôr um string numa List<int> (veja a seção 2). O compilador protege a coleção.
Como consertar: converta o valor (int.Parse, aula Parse e TryParse) ou mude o tipo da coleção.
Dictionary
var precos = new Dictionary<string, decimal>();
precos.Add("arroz", 6.50m);
precos.Add("arroz", 7.00m);
$ dotnet run erros4.cs Unhandled exception. System.ArgumentException: An item with the same key has already been added. Key: arroz at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior) at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value) at Program.<Main>$(String[] args) in /home/ana/cs/erros4.cs:line 3
O que significa: "um item com a mesma chave já foi adicionado. Chave: arroz". Num dicionário, cada chave aparece uma vez só. O Dictionary`2 do rastro é o nome interno do Dictionary (o 2 conta os dois tipos entre < >).
Como consertar: para trocar o preço, use precos["arroz"] = 7.00m;, que cria ou substitui. A aula Dictionary mostra também o TryAdd.
Referência
| Coleção | Criar | Acrescentar | Tirar | Pegar |
|---|---|---|---|---|
T[] | int[] a = [1, 2]; | (não dá) | (não dá) | a[0] |
List<T> | List<int> l = [1, 2]; | l.Add(3) | l.Remove(3), l.RemoveAt(0) | l[0] |
Dictionary<K, V> | Dictionary<string, int> d = new(); | d["a"] = 1, d.Add("a", 1) | d.Remove("a") | d["a"], d.TryGetValue("a", out var v) |
HashSet<T> | HashSet<int> h = [1, 2]; | h.Add(3) | h.Remove(3) | (sem posição) h.Contains(3) |
Queue<T> | Queue<int> q = new(); | q.Enqueue(1) | q.Dequeue() | q.Peek() (o primeiro) |
Stack<T> | Stack<int> s = new(); | s.Push(1) | s.Pop() | s.Peek() (o de cima) |
LinkedList<T> | LinkedList<int> ll = new(); | ll.AddLast(1), ll.AddFirst(0) | ll.Remove(1) | ll.First, ll.Last |
Exercícios
1. Quantos itens
Complete para mostrar quantos itens a lista tem:
2. O tipo da lista
Complete para criar uma lista de preços em reais:
3. Entrar na fila
Complete com o método que põe alguém no fim de uma Queue:
4. A Ana quer achar o telefone de um cliente pelo CPF, entre milhares de clientes. Qual coleção?
5. Um editor de texto guarda as últimas ações para o "desfazer" (Ctrl+Z), que desfaz a mais recente primeiro. Qual coleção?
6. Por que List<int> é melhor que ArrayList?
Missão final: o caixa está atendendo o último que chegou primeiro, porque a coleção é uma pilha. Troque a Stack por uma Queue (e Push/Pop por Enqueue/Dequeue) para atender por ordem de chegada: Ana, Bia, Caio.
Stack<string> fila = new();
fila.Push("Ana");
fila.Push("Bia");
fila.Push("Caio");
while (fila.Count > 0)
{
Console.WriteLine($"Atendendo {fila.Pop()}");
}
Queue<string> fila = new();
fila.Enqueue("Ana");
fila.Enqueue("Bia");
fila.Enqueue("Caio");
while (fila.Count > 0)
{
Console.WriteLine($"Atendendo {fila.Dequeue()}");
}
Resumo
- Uma coleção guarda vários valores. O array tem tamanho fixo; as coleções crescem e encolhem.
- O
<T>diz o tipo dos itens:List<string>,Dictionary<string, decimal>. O compilador não deixa entrar outro tipo (CS1503). Esqueceu o tipo?CS0305. List: em ordem, com posição (a escolha padrão).Dictionary: pela chave.HashSet: sem repetidos.Queue: fila.Stack: pilha.LinkedList: inserções no meio.- Comum a todas:
Count(no array,Length),Contains,Clear,foreach.[ … ]criaListeHashSetjá com itens. - Use as coleções de
System.Collections.Generic;ArrayListeHashtablesão do tempo antes dos genéricos. HashSeteDictionarynão prometem ordem.