C# - Cuidado com a execução adiada
 Vamos rever um conceito básico mas importante da linguagem C# : a execução adiada

A Execução Adiada é uma tradução ao termo Deferred Execution que significa uma execução que não foi realizada no momento mas que foi adiada para ser executada posteriormente.

Desta forma a execução adiada (ou deferred execution) refere-se a um conceito onde uma operação, especialmente em consultas LINQ (Language-Integrated Query), não é executada imediatamente quando definida.

 

Em vez disso, a execução da consulta ocorre apenas quando os dados são realmente solicitados, como ao iterar sobre a coleção ou ao materializar o resultado com métodos como .ToList(), .ToArray() ou .First().

 

Nota:  Quando você chama métodos como .ToList(), .ToArray(), .First(), ou outros que forçam a avaliação imediata da consulta, a execução ocorre no momento da chamada, resultando na execução imediata.  

 

Quando usamos consultas LINQ (Language Integrated Query), elas podem possuir dois comportamentos de execução distintos os quais são:

  1. Execução Deferred ou Adiada

  2. Execução Imediata

Quando a LINQ executa uma consulta ela utiliza a execução adiada ou Deferred Execution, e, isso significa que a consulta real não é executada até que os dados sejam realmente requisitados e acessados pela iteração e não quando a consulta é criada.

 

Vantagens da execução adiada:

 

- Eficiência: A execução só acontece quando necessária, evitando processamento desnecessário.

- Flexibilidade: Permite compor consultas dinamicamente antes de executá-las.

 

Esse é o comportamento padrão do LINQ.

 

Execução adiada na linguagem C#

 

Acontece que este comportamento não é afeto apenas à LINQ mas também é um comportamento que pode ser encontrado na linguagem C#.

 

Exemplos de execução adiada no C#:

 

Em C#, os delegates e expressões lambda permitem que uma ação seja definida, mas sua execução seja adiada até que o delegate ou a lambda seja invocada. Isso é um tipo de execução adiada, já que a ação só ocorre quando você chama explicitamente o delegate ou a lambda.

 

O uso de yield return em C# cria um iterador que também utiliza execução adiada. O método que retorna um IEnumerable<T> usando yield não executa imediatamente todas as iterações. Em vez disso, os valores são gerados conforme solicitado.

 

A programação assíncrona em C# com async e await também pode ser vista como uma forma de execução adiada. Uma tarefa (Task) é definida e começa a ser processada de forma assíncrona, mas seu resultado é consumido mais tarde, quando o await é chamado.

 

Assim, espera-se que desenvolvedores experientes estejam bem cientes desse recurso  da plataforma .NET, mas ele pode realmente pegar de surpresa desenvolvedores mais novos.


Resumindo

Métodos que retornam IEnumerable<T> e geram cada resultado não são executados na linha de código que realmente o chama – eles são executados quando a coleção resultante é acessada de alguma forma. Observe o motivo da maioria das instruções LINQ ter esse comportamento.


Como exemplo, veja o trecho de código abaixo:

 

IEnumerable<string> RepetirString5Vezes(string repetir)
{
    if (repetir == null)
        throw new ArgumentNullException(nameof(repetir));
    for (int i = 0; i < 5; i++)
    {
        if (i == 3)
            throw new InvalidOperationException("O número 3 foi alcançado...");
        yield return $"{repetir} - {i}";
    }
}

 

Qual será o resultado obtido para o código a seguir:

 


 var resultado1 = RepetirString5Vezes(null);
 

 

Este código não será executado pois resultado1 não esta sendo usado, logo o corpo do método nunca será chamado.

 

Isso ocorre por que RepetirString5Vezes() retorna um IEnumerable<string> e somente seria executado quando a coleção fosse acessada, mas como resultado1 não é usado isso nunca vai ocorrer.

 

E agora qual o resultado obtido para o código abaixo:  

 


 var resultado2 = RepetirString5Vezes("teste");
 var primeiroItem = resultado2.First();

 

 

Este código também vai se comportar da seguinte forma:


- O método RepetirString5Vezes() será chamada passando o parâmetro 'teste';

- E o valor em resultado2.First() vai ser recuperado;

- No entanto a exceção InvalidOperationException nunca será lançada, ou seja i==3 nunca será verdadeiro. Assim se formos testar para obter a exceção o teste iria falhar;

 

Este recurso é largamente usado nas consultas LINQ.

 

Assim nunca esqueça que na execução adiada a avaliação de uma expressão é atrasada até que seu valor realizado seja realmente

 

Isso vai melhorar muito o desempenho da sua aplicação pois evita a execução desnecessária.

E estamos conversados.

"Celebrai com júbilo ao SENHOR, todas as terras.
Servi ao Senhor com alegria; e entrai diante dele com canto. "
Salmos 101:1-2

Referências:


José Carlos Macoratti