Por que você precisa de yield em C#? O segredo das coleções preguiçosas
Imagine: você está escrevendo um método que deve retornar uma lista de números de 1 a um milhão. A primeira coisa que vem à mente é criar um List<int>, preenchê-lo e retorná-lo. Mas e se o código chamador quiser apenas os primeiros 10 elementos? Você ainda gastou tempo criando um milhão de objetos, ocupou memória — e tudo em vão.
É aqui que entra a palavra-chave yield em C#. Não é apenas açúcar sintático, mas uma ferramenta poderosa para criar sequências preguiçosas (lazy). Neste artigo, vamos analisar yield return e yield break com exemplos reais, entender como funcionam internamente e aprender a aplicá-los no desenvolvimento do dia a dia.
Este tutorial é baseado em uma pergunta real do Stack Overflow que reuniu milhares de visualizações. Nós o adaptamos para conteúdo educacional, adicionando exemplos passo a passo de código C# e dicas práticas úteis.
O que é yield em C#? Em palavras simples
yield é uma palavra-chave contextual em C# que informa ao compilador: "Este método é um iterador". Quando você escreve yield return, o método não executa tudo de uma vez. Em vez disso, ele retorna os elementos um por um, conforme solicitados pelo código chamador. Isso é chamado de execução preguiçosa.
Ao contrário do return comum, que encerra o método, yield return pausa o método, salva seu estado e retorna o elemento atual. Na próxima chamada, o método continua de onde parou.
Sintaxe: yield return e yield break
Em C#, existem duas formas de yield:
- yield return — retorna o próximo elemento da sequência.
- yield break — encerra imediatamente a iteração (como
breakem um loop).
O método que usa yield deve retornar um destes tipos:
IEnumerable<T>IEnumerableIAsyncEnumerable<T>(a partir do C# 8.0)IEnumerator<T>ouIEnumerator
Primeiro exemplo: do simples ao complexo
Vamos escrever um iterador simples que gera números de 1 a 5:
public static IEnumerable<int> GetNumbers(){ yield return 1; yield return 2; yield return 3; yield return 4; yield return 5;}
// Uso:foreach (int number in GetNumbers()){ Console.WriteLine(number);}Saída:
12345À primeira vista, parece uma lista comum. Mas a diferença é enorme: cada elemento é gerado apenas quando solicitado pelo foreach. Nenhuma alocação de memória para a lista inteira!
Como funciona internamente?
O compilador C# transforma o método com yield em uma máquina de estados (state machine) completa. Ele cria uma classe que implementa IEnumerable<T> e IEnumerator<T>. Essa classe armazena:
- O estado atual (em qual linha do método paramos).
- As variáveis locais do método.
- Uma referência ao objeto atual (
this), se o método não for estático.
Cada vez que MoveNext() é chamado (e isso é feito pelo foreach), a máquina de estados retoma a execução de onde parou.
Exemplo real: leitura de um arquivo grande
Imagine que você precisa processar um arquivo de log de 10 GB. Se você ler tudo na memória, o programa falhará com OutOfMemoryException. Com yield, você pode ler o arquivo linha por linha e processar cada linha em tempo real:
public static IEnumerable<s