2012-01-30 15 views
53

Necesito iterar sobre una Lista de objetos, haciendo algo solo para los objetos que tienen una propiedad booleana establecida en verdadero. Me estoy debatiendo entre este códigoLINQ + Foreach vs Foreach + Si

foreach (RouteParameter parameter in parameters.Where(p => p.Condition)) 
{ //do something } 

y este código

foreach (RouteParameter parameter in parameters) 
{ 
    if !parameter.Condition 
    continue; 
    //do something 
} 

El primer código es, obviamente, más limpio, pero sospecho que va a un bucle sobre la lista dos veces - una vez para la consulta y una vez para el foreach. Esta no será una gran lista, así que no estoy demasiado preocupado por el rendimiento, pero la idea de repetir dos veces solo errores me.

Pregunta: ¿Existe una manera clara/bonita de escribir esto sin bucles dos veces?

+7

Resulta que yo había entendido mal la forma en que funciona ejecución diferida de LINQ, y estas formas son en realidad idénticos en ejecución. Ojalá pudiera marcar múltiples respuestas, porque todos los que están a continuación agregan algo. – Joel

Respuesta

122

Jon Skeet a veces hace una demostración LINQ de acción real para explicar cómo funciona esto. Imagina que tienes tres personas en el escenario. A la izquierda tenemos a un tipo que tiene una baraja barajada. En el medio tenemos a un tipo que solo pasa las tarjetas rojas, y a la derecha, tenemos un tipo que quiere cartas.

El tipo de la derecha golpea al tipo en el medio. El tipo en el medio golpea al tipo de la izquierda. El hombre de la izquierda le da una carta al chico del medio. Si es negro, el tipo que está en el medio lo tira al suelo y lo golpea de nuevo hasta que recibe una tarjeta roja, que luego le entrega al tipo de la derecha. Entonces el tipo de la derecha golpea al tipo en el medio otra vez.

Esto continúa hasta que el hombre de la izquierda se queda sin cartas.

La plataforma no se ha recorrido de principio a fin más de una vez. Sin embargo, tanto el hombre de la izquierda como el del medio manejaban 52 cartas, y el de la derecha manejaba 26 cartas. Hubo un total de 52 + 52 + 26 operaciones en las tarjetas, pero el mazo solo se conectó una vez.

Su versión "LINQ" y la versión "continuar" son la misma cosa; si tuviera

foreach(var card in deck) 
{ 
    if (card.IsBlack) continue; 
    ... use card ... 

entonces hay 52 operaciones que recuperan cada carta de la baraja, 52 operaciones que probar para ver si cada tarjeta es de color negro, y 26 operaciones que actúan sobre la tarjeta roja. Lo mismo exactamente.

+10

+1: lindo fondo * live-action * ¡ejemplo! –

+0

Debería señalar que no es mágico mantener esta lista enumerada dos veces. La ejecución diferida se menciona en otras respuestas, así que busque una explicación de * why * que funcione de la manera descrita tan elocuentemente por @Eric. – hemp

+0

Realmente me gustó la forma en que explicaste esto. – Tarik

35

La mayoría de los operadores de Linq como Where están implementados para admitir la ejecución diferida y diferida . En su ejemplo, la lista se repetirá solo una vez porque el enumerador que se encuentra detrás del IEnumerable devuelto por Where enumerará la lista hasta que encuentre un elemento que coincida con el predicado, lo cederá y solo continuará cuando se le solicite el siguiente elemento.

Desde el punto de vista del código, preferiría la variante utilizando where, aunque podría argumentarse que podría declarar un local para parameters.Where(p => p.Condition).

La serie Edulinq de Jon Skeet es muy recomendada, la lectura de algunas partes de esto debería ayudarle con su comprensión de los operadores LINQ.

26

En realidad, no es "bucle dos veces". La cláusula .Where utiliza ejecución diferida. En otras palabras, prácticamente no se realiza ningún trabajo cuando llama al .Where, pero cuando itera sobre el resultado, iterará sobre la lista original y solo pasará elementos que coincidan con su condición. Si se piensa en ello en términos de cómo el código es ejecutado, que está haciendo efectivamente esto:

Func<Parameter, bool> matchesCondition = p => p.Condition; 
foreach(var parameter in parameters) 
{ 
    if(matchesCondition(parameter)) 
    { 
     ... 
    } 
} 

Como una cuestión de estilo, yo personalmente prefiero algo más como:

var matchingParameters = parameters.Where(p => p.Condition); 
foreach(var parameter in matchingParameters) 
{ 
} 
-2

Yo prefiero esta:

theList.Where(itm => itm.Condition).ToList().ForEach(itmFE => { itmFe.DoSomething(); }); 
+8

Este * está * enumerando la lista dos veces. Exactamente lo que el OP * no * quería. –

+0

Y me pregunto por qué harías eso. La respuesta dada anteriormente es brillante – Aakash