Modos de execução do método - streaming imediato, adiado, não streaming adiado

Execução adiada vs execução imediata

Alguns métodos LINQ retornam um objeto de consulta. Este objeto não contém os resultados da consulta; em vez disso, tem todas as informações necessárias para gerar esses resultados:

var list = new List<int>() {1, 2, 3, 4, 5};
var query = list.Select(x => {
    Console.Write($"{x} ");
    return x;
});

A consulta contém uma chamada para Console.Write, mas nada foi enviado para o console. Isso ocorre porque a consulta ainda não foi executada e, portanto, a função passada para Select nunca foi avaliada. Isso é conhecido como execução adiada – a execução da consulta é atrasada até um pouco mais tarde ponto.

Outros métodos LINQ forçam uma execução imediata da consulta; esses métodos executam a consulta e geram seus valores:

var newList = query.ToList();

Neste ponto, a função passada para Select será avaliada para cada valor na lista original, e o seguinte será enviado para o console:

1 2 3 4 5


Geralmente, os métodos LINQ que retornam um único valor (como Max ou Count), ou que retornam um objeto que realmente contém os valores (como ToList ou ToDictionary) são executados imediatamente.

Métodos que retornam um IEnumerable<T> ou IQueryable<T> estão retornando o objeto de consulta e permitem adiar a execução até um momento posterior.

Se um método LINQ específico força uma consulta a ser executada imediatamente ou não, pode ser encontrado em MSDN – C# ou VB. NET.

Modo de streaming (avaliação lenta) versus modo não streaming (avaliação ansiosa)

Dos métodos LINQ que usam execução adiada, alguns exigem que um único valor seja avaliado por vez. O seguinte código:

var lst = new List<int>() {3, 5, 1, 2};
var streamingQuery = lst.Select(x => {
    Console.WriteLine(x);
    return x;
});
foreach (var i in streamingQuery) {
    Console.WriteLine($"foreach iteration value: {i}");
}

emitirá:

3 valor de iteração foreach: 3 5 valor de iteração foreach: 5 1 valor de iteração foreach: 1 2 valor de iteração foreach: 2

porque a função passada para Select é avaliada em cada iteração do foreach. Isso é conhecido como modo de streaming ou avaliação lenta.


Outros métodos LINQ – operadores de classificação e agrupamento – exigem que todos os valores sejam avaliados, antes que possam retornar qualquer valor:

var nonStreamingQuery = lst.OrderBy(x => {
    Console.WriteLine(x);
    return x;
});
foreach (var i in nonStreamingQuery) {
    Console.WriteLine($"foreach iteration value: {i}");
}

emitirá:

3 5 1 2 valor de iteração foreach: 1 valor de iteração foreach: 2 valor de iteração foreach: 3 valor de iteração foreach: 5

Nesse caso, como os valores devem ser gerados para o foreach em ordem crescente, todos os elementos devem ser avaliados primeiro, para determinar qual é o menor, qual é o próximo menor, e assim por diante. Isso é conhecido como modo não streaming ou avaliação antecipada .


Se um método LINQ específico usa o modo streaming ou não streaming, pode ser encontrado em MSDN – C# ou VB.NET .

Benefícios da execução adiada - construindo consultas

A execução adiada permite combinar diferentes operações para construir a consulta final, antes de avaliar os valores:

var list = new List<int>() {1,1,2,3,5,8};
var query = list.Select(x => x + 1);

Se executarmos a consulta neste ponto:

foreach (var x in query) {
    Console.Write($"{x} ");
}

teríamos a seguinte saída:

2 2 3 4 6 9

Mas podemos modificar a consulta adicionando mais operadores:

Console.WriteLine();
query = query.Where(x => x % 2 == 0);
query = query.Select(x => x * 10);

foreach (var x in query) {
    Console.Write($"{x} ");
}

Resultado:

20 20 40 60

Benefícios da execução adiada - consulta de dados atuais

Com a execução adiada, se os dados a serem consultados forem alterados, o objeto de consulta usará os dados no momento da execução, não no momento da definição.

var data = new List<int>() {2, 4, 6, 8};
var query = data.Select(x => x * x);

Se executarmos a consulta neste ponto com um método imediato ou foreach, a consulta operará na lista de números pares.

No entanto, se alterarmos os valores na lista:

data.Clear();
data.AddRange(new [] {1, 3, 5, 7, 9});

ou mesmo se atribuirmos uma nova lista a data:

data = new List<int>() {1, 3, 5, 7, 9};

e, em seguida, execute a consulta, a consulta operará no novo valor de data:

foreach (var x in query) {
    Console.Write($"{x} ");
}

e produzirá o seguinte:

1 9 25 49 81