Semântica da avaliação tardia
Entenda a execução adiada dos iteradores.
Semântica da avaliação tardia é uma aula grátis de C# Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de C# Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de C# Academy inclui 4 aulas no total.
Execução adiada
Os iteradores e a query do LINQ usam execução adiada: definir a query quase não faz nada. O trabalho só acontece quando você realmente enumera o resultado, item por item.
Nada é executado até enumerar
Chamar um método iterador não executa o seu corpo. O corpo começa a ser executado somente quando um foreach (ou outro consumidor) solicita o primeiro valor.
using System;
using System.Collections.Generic;
public class Program
{
static IEnumerable<int> Numbers()
{
Console.WriteLine("-- iterator body started --");
yield return 1;
yield return 2;
}
public static void Main()
{
var seq = Numbers();
Console.WriteLine("query created, body not run yet");
foreach (var n in seq) Console.WriteLine(n);
}
}Os valores são produzidos um de cada vez
Cada iteração executa apenas o suficiente do iterador para produzir o próximo valor. Esse modelo baseado em solicitações mantém o uso de memória baixo, mesmo para sequências enormes.
using System;
using System.Collections.Generic;
public class Program
{
static IEnumerable<int> Trace()
{
for (int i = 1; i <= 3; i++)
{
Console.WriteLine("producing " + i);
yield return i;
}
}
public static void Main()
{
foreach (var n in Trace())
Console.WriteLine("consumed " + n);
}
}Reenumerar executa novamente
Uma sequência adiada não tem um resultado armazenado em cache. Enumerá-la duas vezes executa o iterador duas vezes, o que é importante se a origem mudar ou se o trabalho for caro.
using System;
using System.Collections.Generic;
public class Program
{
static int _calls = 0;
static IEnumerable<int> Counter()
{
_calls++;
yield return _calls;
}
public static void Main()
{
var seq = Counter();
foreach (var n in seq) Console.WriteLine(n);
foreach (var n in seq) Console.WriteLine(n);
Console.WriteLine("total runs: " + _calls);
}
}Capturando o estado atual
Como a execução é adiada, um iterador lê sua origem no momento da enumeração, não no momento da definição. As alterações feitas depois da definição da query ficam visíveis.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var list = new List<int> { 1, 2 };
IEnumerable<int> doubled = Double(list);
list.Add(3); // added before enumeration
foreach (var n in doubled) Console.WriteLine(n);
}
static IEnumerable<int> Double(IEnumerable<int> nums)
{
foreach (var n in nums) yield return n * 2;
}
}Forçando a execução imediata
Para criar um instantâneo dos resultados, materialize a sequência com ToList() ou ToArray(). Isso executa o iterador uma vez e armazena os valores, desvinculando-os de alterações posteriores na origem.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var list = new List<int> { 1, 2 };
List<int> snapshot = Double(list).ToList(); // executed now
list.Add(99);
Console.WriteLine(string.Join(",", snapshot));
}
static IEnumerable<int> Double(IEnumerable<int> nums)
{
foreach (var n in nums) yield return n * 2;
}
}A interrupção antecipada economiza trabalho
Como os consumidores solicitam valores de forma preguiçosa, uma operação como first pode parar cedo. O produtor nunca calcula valores que não são solicitados.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
static IEnumerable<int> Numbers()
{
for (int i = 1; ; i++)
{
Console.WriteLine("checking " + i);
yield return i;
}
}
public static void Main()
{
int firstBig = Numbers().First(n => n > 3);
Console.WriteLine("found " + firstBig);
}
}Exceções adiadas
Uma exceção dentro de um iterador não é lançada quando o método é chamado, mas quando a enumeração chega à linha que falha. Isso pode surpreendê-lo se você esperar uma validação antecipada.
using System;
using System.Collections.Generic;
public class Program
{
static IEnumerable<int> Risky(int divisor)
{
yield return 10 / divisor; // throws only during enumeration
}
public static void Main()
{
var seq = Risky(0);
Console.WriteLine("no error yet");
try { foreach (var n in seq) Console.WriteLine(n); }
catch (DivideByZeroException) { Console.WriteLine("caught during enumeration"); }
}
}Composição eficiente de fluxos preguiçosos
Encadear operadores preguiçosos não os executa repetidamente. Cada valor percorre todo o fluxo uma vez, conforme necessário, o que é eficiente e econômico em memória.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var result = Enumerable.Range(1, 1000000)
.Where(n => n % 2 == 0)
.Select(n => n * n)
.Take(3);
foreach (var n in result) Console.WriteLine(n);
}
}Quando materializar
Materialize quando for enumerar várias vezes, quando a origem puder mudar ou quando precisar capturar os resultados antes de liberar um recurso. Caso contrário, mantenha a execução preguiçosa para economizar memória e trabalho.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var evens = Enumerable.Range(1, 10).Where(n => n % 2 == 0).ToList();
// Safe to enumerate many times now
Console.WriteLine("count: " + evens.Count);
Console.WriteLine("sum: " + evens.Sum());
}
}Experimente por si mesmo
Observe diretamente a execução adiada: uma query é definida, a origem é alterada e as mudanças só aparecem nos resultados no momento da enumeração.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var source = new List<int> { 1, 2, 3 };
var query = source.Where(n => n > 1); // not executed yet
source.Add(10);
source.Remove(2);
// Executes now, reflecting all changes
Console.WriteLine(string.Join(", ", query));
}
}Verificação rápida
Raciocine sobre a execução adiada.
Recapitulação
A avaliação preguiçosa (adiada) executa uma sequência somente quando ela é enumerada.
- Definir uma query não faz nenhum trabalho; consumi-la faz.
- Reenumerar executa o iterador novamente.
- As origens são lidas no momento da enumeração, portanto alterações posteriores ficam visíveis.
- As exceções aparecem durante a enumeração.
- Use
ToList()/ToArray()para materializar quando necessário.
Perguntas Frequentes
A aula “Semântica da avaliação tardia” é grátis?
Sim — o texto completo de “Semântica da avaliação tardia” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de C# Academy, atualize para CoddyKit PRO. O curso de C# Academy inclui 4 aulas no total.
O que vou aprender em “Semântica da avaliação tardia”?
Entenda a execução adiada dos iteradores. Você pratica C# Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar C# Academy?
Nenhuma experiência prévia é necessária. C# Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.
Quanto tempo leva a aula “Semântica da avaliação tardia”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de C# Academy?
Sim. Cada aula de C# Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Métodos iteradores com yield return
- yield break e encerramento antecipado
- Semântica da avaliação tardia
- Enumeráveis personalizados