C# Queue<T> (Fila)
Na padaria, quem chega primeiro é atendido primeiro. Ninguém fura a fila, e o caixa não escolhe um cliente do meio. Muitos programas precisam exatamente disso: pedidos esperando para ser preparados, documentos esperando a impressora, mensagens esperando para ser enviadas. Para esses casos, o C# tem a fila: Queue<T>.
- O que é uma fila e a regra "primeiro a entrar, primeiro a sair".
- Entrar na fila com
Enqueue, sair comDequeue, espiar comPeek. - Atender todo mundo até a fila esvaziar, com
while. - O que acontece quando a fila está vazia:
InvalidOperationException: Queue empty. - Os jeitos seguros:
TryDequeueeTryPeek. - Por que o
foreachnão tira ninguém da fila. - A fila com prioridade:
PriorityQueue<TElement, TPriority>.
Todos os programas desta aula rodaram com o .NET 10 na máquina da Ana (usuária ana, computador notebook), na pasta ~/cs, com dotnet run arquivo.cs. Você pode repetir cada um no editor do guia.
A ideia: a fila da padaria
Uma fila tem duas pontas. Quem chega entra no fim; quem é atendido sai do começo:
sai daqui entra aqui
↓ ↓
[ Ana ] [ Bruno ] [ Carla ] ← Davi chegou
começo fim
Essa regra tem um nome em inglês que você vai encontrar muito: FIFO, de First In, First Out, "o primeiro a entrar é o primeiro a sair". Na fila, você não pega o item do meio nem pela posição: só existe "o próximo".
Os nomes dos métodos vêm do inglês queue ("fila", pronuncia-se "quiú"):
| Método | Tradução | Na padaria |
|---|---|---|
Enqueue(x) | "enfileirar" | Um cliente chega e vai para o fim da fila. |
Dequeue() | "desenfileirar" | O primeiro da fila é atendido e sai. O método devolve quem saiu. |
Peek() | "espiar" | O caixa olha quem é o próximo, sem chamá-lo. |
Count | "contagem" | Quantas pessoas estão esperando. |
1. Entrar, espiar e sair
var fila = new Queue<string>();
fila.Enqueue("Ana");
fila.Enqueue("Bruno");
fila.Enqueue("Carla");
Console.WriteLine($"Na fila: {fila.Count}");
Console.WriteLine($"Próxima: {fila.Peek()}");
string atendida = fila.Dequeue();
Console.WriteLine($"Atendida: {atendida}");
Console.WriteLine($"Na fila: {fila.Count}");
Console.WriteLine($"Próxima: {fila.Peek()}");
fila.Enqueue("Davi");
Console.WriteLine(string.Join(" <- ", fila));
Console.WriteLine(fila.Contains("Carla"));
Console.WriteLine(fila);
- new Queue<string>()
- Cria uma fila vazia de textos. O
TdeQueue<T>é o tipo de quem espera na fila (veja Generics). - fila.Enqueue("Ana");
- A Ana entra no fim da fila. Como a fila estava vazia, ela também é a primeira.
- fila.Peek()
- Devolve quem está no começo, sem tirar. Depois do
Peek, a fila continua com 3. - string atendida = fila.Dequeue();
- Tira quem está no começo e devolve. Guardamos a resposta numa variável para usá-la.
- string.Join(" <- ", fila)
- Junta todos os nomes com
" <- "no meio, do começo para o fim. É só para mostrar: não tira ninguém. - fila.Contains("Carla")
- "A Carla está na fila?" Funciona, mas é lento numa fila grande: olha pessoa por pessoa.
$ dotnet run fila.cs Na fila: 3 Próxima: Ana Atendida: Ana Na fila: 2 « o Dequeue tirou a Ana Próxima: Bruno « agora o Bruno é o primeiro Bruno <- Carla <- Davi « o Davi entrou no fim True System.Collections.Generic.Queue`1[System.String]
Repare que o Peek não mudou nada (a fila tinha 3 antes e depois dele), e que o Davi, que chegou por último, ficou no fim, mesmo depois da saída da Ana. Na última linha, como as outras coleções, a fila passada direto para o Console.WriteLine mostra só o nome do tipo.
Criar a fila já com pessoas
Para começar com uma fila cheia, passe uma coleção para o construtor. Os itens entram na ordem em que estão nela:
var fila = new Queue<string>(["Ana", "Bruno", "Carla"]);
O que não funciona é o inicializador com chaves, new Queue<string> { "Ana" }, nem Queue<string> fila = ["Ana"];. Os dois precisam de um método Add, e a fila não tem Add: na fila se entra com Enqueue. O erro está em Quando dá errado.
2. Atender até a fila esvaziar
O padrão mais comum com uma fila: enquanto tiver alguém esperando, atenda o próximo.
var fila = new Queue<string>(["Ana", "Bruno", "Carla"]);
while (fila.Count > 0)
{
string cliente = fila.Dequeue();
Console.WriteLine($"Atendendo {cliente}... faltam {fila.Count}");
}
Console.WriteLine("Fila vazia. Hora do café!");
$ dotnet run atender.cs Atendendo Ana... faltam 2 Atendendo Bruno... faltam 1 Atendendo Carla... faltam 0 Fila vazia. Hora do café!
- while (fila.Count > 0)
- "Enquanto houver alguém na fila" (veja while). Cada volta do laço tira uma pessoa, então em algum momento o
Countchega a 0 e o laço para. - string cliente = fila.Dequeue();
- Chama o próximo. Na primeira volta é a Ana, na segunda o Bruno, na terceira a Carla: sempre na ordem de chegada.
E o foreach?
Dá para percorrer uma fila com foreach, mas ele só olha: ninguém sai da fila.
var fila = new Queue<string>(["Ana", "Bruno"]);
foreach (string pessoa in fila)
{
Console.WriteLine($"Vejo {pessoa} na fila");
}
Console.WriteLine($"Ainda na fila: {fila.Count}");
foreach (string pessoa in fila)
{
fila.Enqueue(pessoa + " (de novo)");
}
$ dotnet run foreach.cs Vejo Ana na fila Vejo Bruno na fila Ainda na fila: 2 « o foreach não tirou ninguém Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute. at System.Collections.Generic.Queue`1.Enumerator.MoveNext() at Program.<Main>$(String[] args) in /home/ana/cs/foreach.cs:line 9 $ echo $? 134
O segundo foreach mostra outra regra, igual à das outras coleções: enquanto um foreach percorre a fila, você não pode mudá-la. O Enqueue funcionou, mas na hora de ir para o próximo (MoveNext) o foreach percebeu e lançou a InvalidOperationException ("a coleção foi modificada; o percurso não pode continuar"), apontando a linha 9, a do foreach. Para atender e receber gente nova ao mesmo tempo, use o while com Dequeue: ele não percorre a fila, só pergunta o Count a cada volta.
Missão: o programa usa foreach e, no fim, a fila continua com 3 pessoas. Troque o foreach por um while com Dequeue, para que cada cliente atendido saia da fila e a última linha seja Na fila: 0.
var fila = new Queue<string>(["Ana", "Bruno", "Carla"]);
foreach (string cliente in fila)
{
Console.WriteLine($"Atendendo {cliente}");
}
Console.WriteLine($"Na fila: {fila.Count}");
var fila = new Queue<string>(["Ana", "Bruno", "Carla"]);
while (fila.Count > 0)
{
string cliente = fila.Dequeue();
Console.WriteLine($"Atendendo {cliente}");
}
Console.WriteLine($"Na fila: {fila.Count}");
3. A fila vazia: Queue empty.
E se o caixa chamar o próximo quando não tem ninguém? No C#, isso é uma exceção:
var fila = new Queue<string>();
fila.Enqueue("Ana");
Console.WriteLine(fila.Dequeue());
Console.WriteLine(fila.Dequeue());
$ dotnet run vazia.cs Ana Unhandled exception. System.InvalidOperationException: Queue empty. at System.Collections.Generic.Queue`1.ThrowForEmptyQueue() at System.Collections.Generic.Queue`1.Dequeue() at Program.<Main>$(String[] args) in /home/ana/cs/vazia.cs:line 5 $ echo $? 134
- InvalidOperationException
- "Exceção de operação inválida": a operação não faz sentido no estado atual do objeto. Não dá para atender ninguém numa fila vazia.
- Queue empty.
- "Fila vazia." A mensagem é curta, mas diz tudo.
- Queue`1.Dequeue()
- Quem lançou foi o
Dequeue. Com oPeeknuma fila vazia, a mensagem é a mesma, e o rastro mostraQueue`1.Peek(). - vazia.cs:line 5
- O segundo
Dequeue: a Ana já tinha saído na linha 4.
Os jeitos seguros: TryDequeue e TryPeek
Como no TryGetValue do Dictionary, os métodos com Try ("tentar") não lançam exceção: devolvem true ou false e entregam o item num parâmetro out (veja ref, out e in).
var fila = new Queue<string>();
fila.Enqueue("Ana");
while (fila.TryDequeue(out string? cliente))
{
Console.WriteLine($"Atendendo {cliente}");
}
if (!fila.TryPeek(out string? proximo))
Console.WriteLine($"Ninguém na fila (proximo = {proximo ?? "null"})");
$ dotnet run try.cs Atendendo Ana Ninguém na fila (proximo = null)
- while (fila.TryDequeue(out string? cliente))
- "Enquanto conseguir tirar alguém, chame essa pessoa de
cliente." Quando a fila esvazia, oTryDequeuedevolvefalsee o laço para. É o mesmo laço da seção 2, numa linha. - string?
- O
?diz que a variável pode ficarnull: é o que ooutrecebe quando a fila está vazia (veja Referências Anuláveis). - proximo ?? "null"
- Mostra a palavra "null" quando
proximoénull(o operador??de Operadores ??, ?. e ??=).
Se você tem certeza de que a fila não está vazia (acabou de testar o Count, por exemplo), Dequeue é direto. Se a fila pode estar vazia, prefira TryDequeue: ele pergunta e tira de uma vez só.
4. Fila com prioridade: PriorityQueue
No pronto-socorro, a ordem não é a de chegada: quem está mais grave passa na frente. Para isso existe a PriorityQueue<TElement, TPriority> ("fila de prioridade"), em que cada item entra com um número de prioridade, e o menor número sai primeiro.
var triagem = new PriorityQueue<string, int>();
triagem.Enqueue("Ana (resfriado)", 3);
triagem.Enqueue("Bruno (braço quebrado)", 1);
triagem.Enqueue("Carla (dor de cabeça)", 4);
triagem.Enqueue("Davi (febre alta)", 2);
while (triagem.TryDequeue(out string? paciente, out int prioridade))
{
Console.WriteLine($"{prioridade}: {paciente}");
}
$ dotnet run prioridade.cs 1: Bruno (braço quebrado) 2: Davi (febre alta) 3: Ana (resfriado) 4: Carla (dor de cabeça)
- PriorityQueue<string, int>
- Dois tipos: o do item (
string, o paciente) e o da prioridade (int). - triagem.Enqueue("…", 3)
- Entra com a prioridade 3. A ordem em que os pacientes chegaram não importa.
- TryDequeue(out … paciente, out int prioridade)
- Tira o de menor prioridade e entrega o item e a prioridade dele.
Se dois pacientes entram com a mesma prioridade, a PriorityQueue não promete que o que chegou primeiro sai primeiro. Se o desempate importa, inclua-o na prioridade (por exemplo, a gravidade e depois a hora de chegada).
Ela também não aceita foreach direto: foreach (var x in triagem) dá error CS1579: A instrução foreach não pode operar em variáveis do tipo 'PriorityQueue<string, int>'…. A única ordem que ela garante é a do Dequeue.
Qual coleção usar?
| Você quer… | Use |
|---|---|
| Atender na ordem de chegada (primeiro a entrar, primeiro a sair) | Queue<T> |
| Atender o mais urgente primeiro | PriorityQueue<TElement, TPriority> |
| Atender o último que chegou (desfazer, voltar) | Stack<T> (Pilha) |
| Pegar qualquer item pela posição | List<T> |
Quando dá errado
InvalidOperationException: Queue empty.
O que significa: um Dequeue() ou Peek() numa fila sem ninguém. O rastro mostra qual dos dois e a linha do seu programa.
Como consertar: teste antes com fila.Count > 0 ou use TryDequeue/TryPeek (seção 3).
InvalidOperationException: Collection was modified; enumeration operation may not execute.
O que significa: você chamou Enqueue ou Dequeue dentro de um foreach que percorre a mesma fila.
Como consertar: para atender, use while (fila.TryDequeue(out var x)), que pode receber gente nova a qualquer momento (seção 2).
error CS1061: ‘Queue<string>’ não contém uma definição para "Add"…
O que significa: você tentou criar a fila com um inicializador entre chaves ou com colchetes. Os dois chamam Add por baixo, e a fila não tem Add:
$ cat colexpr.cs Queue<string> fila = ["Ana", "Bruno"]; $ dotnet run colexpr.cs /home/ana/cs/colexpr.cs(1,22): error CS1061: ‘Queue<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 ‘Queue<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.
Como consertar: passe a coleção para o construtor: new Queue<string>(["Ana", "Bruno"]), ou chame Enqueue para cada um.
error CS0021: Não é possível aplicar a indexação com [] a uma expressão do tipo "Queue<string>"
O que significa: fila[0]. A fila não tem posições: só existe "o próximo".
Como consertar: para ver o primeiro, use Peek(). Se você precisa mesmo de posições, a coleção certa é a List<T>.
error CS0029: Não é possível converter implicitamente tipo "void" em "int"
O que significa: você escreveu int x = fila.Enqueue(2);. O Enqueue não devolve nada (void); quem devolve um item é o Dequeue.
Como consertar: chame fila.Enqueue(2); sozinho, numa linha.
Referência rápida
| Escrita | O que faz |
|---|---|
new Queue<T>() | Fila vazia |
new Queue<T>(colecao) | Fila com os itens da coleção, na ordem |
f.Enqueue(x) | Põe x no fim |
f.Dequeue() | Tira e devolve o primeiro; InvalidOperationException se vazia |
f.Peek() | Devolve o primeiro sem tirar; InvalidOperationException se vazia |
f.TryDequeue(out var x) | Tira se puder; devolve bool |
f.TryPeek(out var x) | Espia se puder; devolve bool |
f.Count | Quantos esperam |
f.Contains(x) | x está na fila? (lento: olha um por um) |
f.Clear() | Esvazia |
f.ToArray() | Cópia em array, do primeiro ao último |
new PriorityQueue<TElement, TPriority>() | Fila em que sai primeiro a menor prioridade |
Exercícios
1. Entrar na fila
Complete para o Davi entrar no fim da fila:
2. Ver sem tirar
Complete para ver quem é o próximo, sem tirá-lo da fila:
3. Entram na fila, nesta ordem, "A", "B" e "C". Depois há um Dequeue(). Quem sai?
4. O que acontece com new Queue<int>().Dequeue()?
5. Uma fila tem 3 pessoas. Depois de um foreach que mostra cada uma, quantas continuam na fila?
Missão final: a fila da impressora
Missão: três documentos foram mandados para a impressora, nesta ordem: boleto.pdf, trabalho.docx e foto.png. Ponha os três na fila com Enqueue. Depois, tire um por um com Dequeue (ou TryDequeue) até a fila esvaziar, mostrando Imprimindo NOME, e conte quantos foram impressos.
Imprimindo boleto.pdf Imprimindo trabalho.docx Imprimindo foto.png Impressos: 3
var impressora = new Queue<string>();
int impressos = 0;
// ponha os três documentos na fila
// imprima até a fila esvaziar
Console.WriteLine($"Impressos: {impressos}");
var impressora = new Queue<string>();
int impressos = 0;
impressora.Enqueue("boleto.pdf");
impressora.Enqueue("trabalho.docx");
impressora.Enqueue("foto.png");
while (impressora.Count > 0)
{
string documento = impressora.Dequeue();
Console.WriteLine($"Imprimindo {documento}");
impressos++;
}
Console.WriteLine($"Impressos: {impressos}");
Resumo
- Uma
Queue<T>é uma fila: o primeiro a entrar é o primeiro a sair (FIFO). Enqueuepõe no fim;Dequeuetira e devolve o primeiro;Peekmostra o primeiro sem tirar.- Para atender todos:
while (fila.Count > 0)comDequeue, ouwhile (fila.TryDequeue(out var x)). DequeueePeeknuma fila vazia lançamInvalidOperationException: Queue empty.;TryDequeueeTryPeekdevolvemfalse.- O
foreachsó olha a fila; mudar a fila dentro dele dáCollection was modified. - A fila não tem
Addnem posições: crie comnew Queue<T>(colecao). - Para o mais urgente primeiro, use
PriorityQueue<TElement, TPriority>(sai a menor prioridade).