Se eu escrever algo como isto:
var things = mythings
.Where(x => x.IsSomeValue)
.Where(y => y.IsSomeOtherValue)
É o mesmo que:
var results1 = new List<Thing>();
foreach(var t in mythings)
if(t.IsSomeValue)
results1.Add(t);
var results2 = new List<Thing>();
foreach(var t in results1)
if(t.IsSomeOtherValue)
results2.Add(t);
Ou existe alguma mágica embaixo das cobertas que funciona mais assim:
var results = new List<Thing>();
foreach(var t in mythings)
if(t.IsSomeValue && t.IsSomeOtherValue)
results.Add(t);
Ou é algo completamente diferente?
Respostas:
As consultas LINQ são preguiçosas . Isso significa o código:
faz muito pouco. O enumerável original (
mythings
) só é enumerado quando o enumerável resultante (things
) é consumido, por exemplo, por umforeach
loop.ToList()
, ou.ToArray()
.Se você ligar
things.ToList()
, é aproximadamente equivalente ao seu último código, talvez com alguma sobrecarga (geralmente insignificante) dos enumeradores.Da mesma forma, se você usar um loop foreach:
É semelhante no desempenho a:
Algumas das vantagens de desempenho da abordagem de preguiça para enumeráveis (ao contrário de calcular todos os resultados e armazená-los em uma lista) são que ele usa muito pouca memória (uma vez que apenas um resultado é armazenado por vez) e que não há resultados significativos. custo inicial.
Se o enumerável é apenas parcialmente enumerado, isso é especialmente importante. Considere este código:
A maneira como o LINQ é implementado
mythings
será enumerado apenas até o primeiro elemento que corresponda às condições where. Se esse elemento estiver no início da lista, isso pode ser um grande aumento de desempenho (por exemplo, O (1) em vez de O (n)).fonte
foreach
é que o LINQ usa chamadas de delegação, com alguma sobrecarga. Isso pode ser significativo quando as condições são executadas muito rapidamente (o que geralmente ocorre).ToList
ouToArray
. Se tal coisa tivesse sido incorporada adequadamenteIEnumerable
, seria possível solicitar uma lista para "capturar" todos os aspectos que possam mudar no futuro sem ter que gerar tudo.O código a seguir:
É equivalente a nada, por causa da avaliação preguiçosa, nada vai acontecer.
É diferente, porque a avaliação será iniciada.
Cada item
mythings
será entregue ao primeiroWhere
. Se passar, será dado ao segundoWhere
. Se passar, fará parte da saída.Portanto, isso se parece mais com isso:
fonte
Além da execução adiada (que as outras respostas já explicam, apenas apontarei outro detalhe), é mais como no seu segundo exemplo.
Vamos imaginar que você chama
ToList
dethings
.A implementação de
Enumerable.Where
retornos aEnumerable.WhereListIterator
. Quando você chamaWhere
issoWhereListIterator
(também conhecido como encadeamento deWhere
chamadas), não chama maisEnumerable.Where
, masEnumerable.WhereListIterator.Where
, na verdade, combina os predicados (usandoEnumerable.CombinePredicates
).Então é mais como
if(t.IsSomeValue && t.IsSomeOtherValue)
.fonte
Não, não é a mesma coisa. No seu exemplo,
things
é umIEnumerable
, que neste momento ainda é apenas um iterador, não uma matriz ou lista real. Além disso, comothings
não é usado, o loop nunca é avaliado. O tipoIEnumerable
permite iterar através dos elementosyield
-ed pelas instruções do Linq e processá-los ainda mais com mais instruções, o que significa que, no final, você realmente tem apenas um loop.Mas assim que você adiciona uma instrução como
.ToArray()
ou.ToList()
, você está ordenando a criação de uma estrutura de dados real, colocando assim limites à sua cadeia.Consulte esta pergunta SO relacionada: /programming/2789389/how-do-i-implement-ienumerable
fonte