Yield return em C#: quando o código funciona "de forma preguiçosa" e isso é legal
\n\nImagine que você está escrevendo um programa que precisa processar um milhão de linhas de um arquivo de log. Você honestamente carrega tudo em uma lista List<string>, passa para um método de filtragem, e então — puf! — o programa trava, a memória vaza, e o usuário nervosamente aperta Ctrl+Alt+Del.
Familiar? Se você já enfrentou essa situação pelo menos uma vez, então está na hora de conhecer a "magia preguiçosa" do C# — o operador yield return. Isso não é apenas açúcar sintático, mas uma ferramenta poderosa que pode mudar radicalmente a forma como você trabalha com coleções.
Neste artigo, vamos analisar o uso correto do yield return com exemplos reais, explorar os bastidores e descobrir quando usá-lo, e quando é melhor evitar. Vamos lá!
\n\nO que é yield return e como funciona?
\n\nExplicando de forma simples, yield return é uma forma de dizer ao compilador: "Ei, eu não quero retornar a coleção inteira de uma vez. Vou entregar os elementos um por um, conforme necessário". Essa abordagem é chamada de computação preguiçosa (lazy evaluation).
Aqui está um exemplo clássico:
\n\npublic IEnumerable<int> GetNumbers()\n{\n yield return 1;\n yield return 2;\n yield return 3;\n}\n\nAo chamar este método, você não obtém um array [1, 2, 3]. Em vez disso, você obtém um iterador que entregará os números um por um, quando solicitado. Cada chamada a MoveNext() avança a execução do método até o próximo yield return.
Isso é como uma esteira: os elementos são produzidos apenas quando o consumidor precisa deles. Sem cópias desnecessárias, sem listas gigantescas na memória — apenas trabalho limpo e eficiente.
\n\nPor que yield return não é apenas "retornar uma lista"?
\n\nMuitos iniciantes pensam: "Por que eu usaria yield return se posso simplesmente criar um List e retorná-lo?". Vamos analisar com um exemplo concreto.
Imagine que você precisa obter os primeiros 10 números pares de uma sequência infinita. Com yield return, isso é feito de forma elegante:
public IEnumerable<int> GetEvenNumbers()\n{\n int i = 0;\n while (true)\n {\n yield return i;\n i += 2;\n }\n}\n\n// Uso\nvar firstTenEven = GetEvenNumbers().Take(10).ToList();\n\nTente fazer o mesmo com List<int> — ou você entrará em um loop infinito, ou terá que armazenar todos os números gerados na memória. Yield return nos dá a capacidade de trabalhar com sequências potencialmente infinitas sem sobrecarregar a memória RAM.
Exemplo prático: leitura de um arquivo grande
\n\nVoltemos ao nosso arquivo de log de um milhão de linhas. Veja como os iniciantes geralmente escrevem:
\n\npublic List<string> ReadLogLines(string path)\n{\n var lines = new List<string>();\n using (var reader = new StreamReader(path))\n {\n string line;\n while ((line = reader.ReadLine()) != null)\n {\n lines.Add(line);\n }\n }\n return lines;\n}\n\nProblema: o arquivo inteiro é carregado na memória. Se o arquivo pesa 500 MB, a lista pesará aproximadamente o mesmo. E se você só precisa filtrar as linhas com erros? Mesmo assim, você carregará tudo.
\n\nAgora, o uso correto de yield return:
public IEnumerable<string> ReadLogLines(string path)\n{\n using (var reader = new StreamReader(path))\n {\n string line;\n while ((line = reader.ReadLine()) != null)\n {\n yield return line;\n }\n }\n}\n\n// Filtragem sem carregar o arquivo inteiro\nvar errorLines = ReadLogLines("log.txt")\n .Where(line => line.Contains("ERROR"))\n .Take(100);<