Si escribo algo como esto:
var things = mythings
.Where(x => x.IsSomeValue)
.Where(y => y.IsSomeOtherValue)
¿Es esto lo mismo 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);
¿O hay algo de magia debajo de las cubiertas que funciona más así:
var results = new List<Thing>();
foreach(var t in mythings)
if(t.IsSomeValue && t.IsSomeOtherValue)
results.Add(t);
¿O es algo completamente diferente?
Respuestas:
Las consultas de LINQ son perezosas . Eso significa el código:
hace muy poco El enumerable original (
mythings
) solo se enumera cuandothings
se consume el enumerable resultante ( ), por ejemplo, mediante unforeach
bucle.ToList()
, o.ToArray()
.Si llama
things.ToList()
, es más o menos equivalente a su último código, con quizás una sobrecarga (generalmente insignificante) de los enumeradores.Del mismo modo, si usa un bucle foreach:
Es similar en rendimiento a:
Algunas de las ventajas de rendimiento del enfoque de la pereza para los enumerables (en lugar de calcular todos los resultados y almacenarlos en una lista) es que utiliza muy poca memoria (ya que solo se almacena un resultado a la vez) y que no hay un aumento significativo -costo por adelantado.
Si lo enumerable solo se enumera parcialmente, esto es especialmente importante. Considera este código:
La forma en que se implementa LINQ
mythings
solo se enumerará hasta el primer elemento que coincida con sus condiciones where. Si ese elemento está al principio de la lista, esto puede ser un gran aumento de rendimiento (por ejemplo, O (1) en lugar de O (n)).fuente
foreach
es que LINQ usa invocaciones de delegado, que tienen algo de sobrecarga. Esto puede ser significativo cuando las condiciones se ejecutan muy rápidamente (lo que a menudo hacen).ToList
oToArray
. Si tal cosa se hubiera incorporado correctamenteIEnumerable
, habría sido posible pedir una lista para "capturar" cualquier aspecto que pudiera cambiar en el futuro sin tener que generar todo.El siguiente código:
Es equivalente a nada, debido a la evaluación perezosa, no pasará nada.
Es diferente, porque la evaluación será lanzada.
Cada artículo de
mythings
será entregado al primeroWhere
. Si pasa, se le dará al segundoWhere
. Si pasa, será parte de la salida.Entonces esto se parece más a esto:
fuente
Dejando de lado la ejecución diferida (que las otras respuestas ya explican, solo señalaré otro detalle), es más como en su segundo ejemplo.
Imaginemos que llame
ToList
athings
.La implementación de
Enumerable.Where
devoluciones aEnumerable.WhereListIterator
. Cuando invocasWhere
esoWhereListIterator
(también conocido como encadenamiento deWhere
llamadas), ya no invocasEnumerable.Where
, sinoEnumerable.WhereListIterator.Where
que, en realidad, combina los predicados (usandoEnumerable.CombinePredicates
).Entonces es más como
if(t.IsSomeValue && t.IsSomeOtherValue)
.fuente
No, no es lo mismo. En su ejemplo
things
es unIEnumerable
, que en este punto todavía es solo un iterador, no una matriz o lista real. Además, dadothings
que no se usa, el ciclo nunca se evalúa. El tipoIEnumerable
permite iterar a través de elementosyield
-ed por instrucciones de Linq y procesarlos aún más con más instrucciones, lo que significa que al final solo tiene un bucle.Pero tan pronto como agregue una instrucción como
.ToArray()
o.ToList()
, está ordenando la creación de una estructura de datos real, poniendo límites a su cadena.Consulte esta pregunta SO relacionada: /programming/2789389/how-do-i-implement-ienumerable
fuente