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.

Você vai aprender
  • Por que o array tem tamanho fixo, e o que isso atrapalha.
  • O que quer dizer o <T> de List<T>.
  • Para que serve cada coleção: List, Dictionary, HashSet, Queue, Stack e LinkedList.
  • 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, CS1503 e 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 diaEm C#O que ela faz bem
Caixa de ovos (tamanho fixo)array T[]Guardar uma quantidade conhecida, por posição
Lista de comprasList<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 padariaQueue<T>Primeiro a entrar, primeiro a sair
Pilha de pratosStack<T>Último a entrar, primeiro a sair
Trem de vagões engatadosLinkedList<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:

Este código tem um erro de propósito.
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.

Este código tem um erro de propósito.
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. O new() sem o tipo repetido é o new "pelo tipo de destino", e ["arroz"] = 6.50m põe um par chave → valor. Aula: Dictionary.
HashSet<string> · 3 pessoas
Entraram cinco nomes, mas os repetidos foram ignorados: Ana, Bia e Caio. 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.

O 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çãoO que fazOnde existe
.CountQuantos 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)AcrescentaList, HashSet, Dictionary (Add(chave, valor)); na fila e na pilha o nome é outro (Enqueue, Push)
.Clear()EsvaziaTodas as coleções
foreach (var x in c)Passa por todos os itensTodas, e arrays (aula IEnumerable e foreach)
[a, b, c]Cria já com itens C# 12List, 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çãoTodas, 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 CPFDictionary<TKey, TValue>
garantir que não há repetidos, ou perguntar "está lá?" muitas vezesHashSet<T>
atender por ordem de chegadaQueue<T>
desfazer a última ação, voltar ao anteriorStack<T>
inserir e remover muito no meio de uma lista enormeLinkedList<T>
na dúvidaList<T>: troque depois, se precisar

A tabela completa, com o custo de cada operação, está em Referência: Coleções.


Quando dá errado

CS1061: 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.

CS0305: 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 = [];.

CS1503: valor do tipo errado

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.

ArgumentException: chave repetida no 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çãoCriarAcrescentarTirarPegar
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:

Console.WriteLine(compras.);

2. O tipo da lista

Complete para criar uma lista de preços em reais:

List<> precos = [6.50m, 18.90m];

3. Entrar na fila

Complete com o método que põe alguém no fim de uma Queue:

fila.("Ana");

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. [ … ] cria List e HashSet já com itens.
  • Use as coleções de System.Collections.Generic; ArrayList e Hashtable são do tempo antes dos genéricos.
  • HashSet e Dictionary não prometem ordem.