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

Você vai aprender
  • O que é uma fila e a regra "primeiro a entrar, primeiro a sair".
  • Entrar na fila com Enqueue, sair com Dequeue, espiar com Peek.
  • Atender todo mundo até a fila esvaziar, com while.
  • O que acontece quando a fila está vazia: InvalidOperationException: Queue empty.
  • Os jeitos seguros: TryDequeue e TryPeek.
  • Por que o foreach nã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étodoTraduçãoNa 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 T de Queue<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 Count chega 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 o Peek numa fila vazia, a mensagem é a mesma, e o rastro mostra Queue`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, o TryDequeue devolve false e o laço para. É o mesmo laço da seção 2, numa linha.
string?
O ? diz que a variável pode ficar null: é o que o out recebe quando a fila está vazia (veja Referências Anuláveis).
proximo ?? "null"
Mostra a palavra "null" quando proximo é null (o operador ?? de Operadores ??, ?. e ??=).
Qual usar?

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.
Prioridades iguais não têm ordem garantida

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 primeiroPriorityQueue<TElement, TPriority>
Atender o último que chegou (desfazer, voltar)Stack<T> (Pilha)
Pegar qualquer item pela posiçãoList<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

EscritaO 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.CountQuantos 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:

fila.("Davi");

2. Ver sem tirar

Complete para ver quem é o próximo, sem tirá-lo da fila:

string proximo = 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).
  • Enqueue põe no fim; Dequeue tira e devolve o primeiro; Peek mostra o primeiro sem tirar.
  • Para atender todos: while (fila.Count > 0) com Dequeue, ou while (fila.TryDequeue(out var x)).
  • Dequeue e Peek numa fila vazia lançam InvalidOperationException: Queue empty.; TryDequeue e TryPeek devolvem false.
  • O foreach só olha a fila; mudar a fila dentro dele dá Collection was modified.
  • A fila não tem Add nem posições: crie com new Queue<T>(colecao).
  • Para o mais urgente primeiro, use PriorityQueue<TElement, TPriority> (sai a menor prioridade).