2008-10-22 10 views
114

Aparentemente hay muchas formas de iterar sobre una colección. Curioso si hay alguna diferencia, o por qué usaría una forma sobre la otra.foreach vs someList.ForEach() {}

Primer tipo:

List<string> someList = <some way to init> 
foreach(string s in someList) { 
    <process the string> 
} 

Otra Manera:

List<string> someList = <some way to init> 
someList.ForEach(delegate(string s) { 
    <process the string> 
}); 

supongo que la parte superior de la cabeza, que en lugar del delegado anónimo utilizo anteriormente, usted tendría un delegado reutilizable podría especificar ...

+0

Sugiero leer [Eric Lipperts blog "foreach" vs "ForEach"] (http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx) –

Respuesta

10

Supongo que la llamada someList.ForEach() podría ser fácilmente paralelizada, mientras que el foreach normal no es tan fácil de ejecutar en paralelo. Puede ejecutar fácilmente varios delegados diferentes en diferentes núcleos, lo que no es tan fácil de hacer con un foreach normal. Sólo mi 2 centavos

+0

¿Qué código agregarías para "paralelizarlo fácilmente"? – Anthony

+1

Creo que quiso decir que el motor de tiempo de ejecución podría paralelizarlo automáticamente. De lo contrario, ambos foreach y .ForEach se pueden paralelizar a mano utilizando un subproceso del grupo en cada delegado de acción –

+0

@Isak, pero eso sería una suposición incorrecta. Si el método anónimo iza a un local o miembro, el tiempo de ejecución no será fácil de paralelizar –

3

usted podría nombrar al delegado anónimo :-)

y se puede escribir la segunda como:

someList.ForEach(s => s.ToUpper()) 

que yo prefiero, y ahorra un montón de escribir.

Como dice Joachim, el paralelismo es más fácil de aplicar a la segunda forma.

1

La segunda manera que mostró utiliza un método de extensión para ejecutar el método de delegado para cada uno de los elementos en la lista.

De esta manera, tiene otra llamada de delegado (= método).

Además, existe la posibilidad de iterar la lista con un para el bucle.

1

Detrás de escena, el delegado anónimo se convierte en un método real para que pueda tener un poco de sobrecarga con la segunda opción si el compilador no eligió alinear la función. Además, las variables locales a las que hace referencia el cuerpo del ejemplo del delegado anónimo cambiarían en la naturaleza debido a los trucos del compilador para ocultar el hecho de que se compila a un nuevo método. Más información aquí sobre cómo C# hace esta magia:

http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx

3

Considere el siguiente article en el rendimiento list.foreach.

+2

Excelente artículo, especialmente comentario útil sobre Array.ForEach. –

+0

siendo este artículo fechado en 2006, ¿la información sigue siendo válida? – FabioG

+0

*** ¿La información es válida? *** – Kiquenet

1

Una cosa de qué preocuparse es cómo salir del método genérico .ForEach - vea this discussion. Aunque el enlace parece decir que de esta manera es el más rápido. No estoy seguro de por qué, creería que serían equivalentes una vez compilados ...

+0

¡Excelente contrapunto! –

3

List.ForEach() se considera que es más funcional.

List.ForEach() dice lo que quieres que se haga. foreach(item in list) también dice exactamente cómo lo quiere hacer. Esto deja List.ForEach libre de cambiar la implementación de cómo parte en el futuro. Por ejemplo, una versión hipotética futura de .Net siempre podría ejecutar List.ForEach en paralelo, bajo la suposición de que en este punto todos tienen una cantidad de núcleos de CPU que generalmente están inactivos.

Por otro lado, foreach (item in list) le da un poco más de control sobre el bucle. Por ejemplo, usted sabe que los elementos se iterarán en algún tipo de orden secuencial, y podría romperse fácilmente en el medio si un elemento cumple alguna condición.

95

Hay una distinción importante y útil entre los dos.

Debido .ForEach utiliza un bucle for para recorrer la colección, esto es válido (edit: antes que .NET 4.5 - la puesta en práctica cambió y ambos tiro):

someList.ForEach(x => { if(x.RemoveMe) someList.Remove(x); }); 

mientras que foreach utiliza una empadronador, así que esto no es válido:

foreach(var item in someList) 
    if(item.RemoveMe) someList.Remove(item); 

tl; d r: NO copie este código en su aplicación!

Estos ejemplos no son una buena práctica, son solo para demostrar las diferencias entre ForEach() y foreach.

Eliminar elementos de una lista dentro de un bucle for puede tener efectos secundarios. El más común se describe en los comentarios a esta pregunta.

Generalmente, si está buscando eliminar varios elementos de una lista, le recomendamos separar la determinación de qué elementos eliminar de la eliminación real. No mantiene compacto su código, pero garantiza que no se pierda ningún elemento.

+26

incluso entonces, debe usar someList.RemoveAll (x => x.RemoveMe) en su lugar –

+2

Con Linq, todo se puede hacer mejor. Solo estaba mostrando un ejemplo de modificación de la colección dentro de foreach ... – Will

+2

RemoveAll() es un método en la lista . –

15

Para la diversión, se salió de lista en el reflector y este es el C# resultante:

public void ForEach(Action<T> action) 
{ 
    if (action == null) 
    { 
     ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); 
    } 
    for (int i = 0; i < this._size; i++) 
    { 
     action(this._items[i]); 
    } 
} 

Del mismo modo, el MoveNext en enumerador que es lo que se utiliza por foreach es la siguiente:

public bool MoveNext() 
{ 
    if (this.version != this.list._version) 
    { 
     ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); 
    } 
    if (this.index < this.list._size) 
    { 
     this.current = this.list._items[this.index]; 
     this.index++; 
     return true; 
    } 
    this.index = this.list._size + 1; 
    this.current = default(T); 
    return false; 
} 

El List.ForEach está mucho más recortado que MoveNext - mucho menos procesamiento - es más probable que JIT se convierta en algo eficiente ...

Además, foreach() asignará un nuevo Enumerator no importa que. El GC es tu amigo, pero si estás haciendo lo mismo foreach repetidamente, esto hará más objetos desechables, en lugar de reutilizar el mismo delegado - PERO - esto es realmente un caso marginal. En el uso típico, verá poca o ninguna diferencia.

+0

No tiene garantía de que el código generado por foreach sea el mismo entre las versiones del compilador. El código generado se puede mejorar con una versión futura. – Anthony

53

Tuvimos algunos códigos aquí (en VS2005 y C# 2.0) donde los ingenieros anteriores hicieron todo lo posible para usar list.ForEach(delegate(item) { foo;}); en lugar de foreach(item in list) {foo; }; para todo el código que escribieron. p.ej. un bloque de código para leer filas de un lector de datos.

Todavía no sé exactamente por qué lo hicieron.

Los inconvenientes de list.ForEach() son:

  • Es más detallado en C 2,0 #. Sin embargo, en C# 3 en adelante, puede usar la sintaxis "=>" para hacer algunas expresiones muy lacras.

  • Es menos familiar. Las personas que tienen que mantener este código se preguntarán por qué lo hiciste de esa manera. Me tomó un tiempo decidir que no había ninguna razón, excepto tal vez para hacer que el escritor pareciera inteligente (la calidad del resto del código lo socavó). También fue menos legible, con el "})" al final del bloque de código de delegado.

  • Vea también el libro de Bill Wagner "Effective C#: 50 Formas específicas para mejorar su C#" donde explica por qué foreach es preferible a otros bucles como for o while - el punto principal es dejar que el compilador decida la mejor forma de construir el ciclo Si una versión futura del compilador logra usar una técnica más rápida, la obtendrá de forma gratuita utilizando foreach y reconstrucción, en lugar de cambiar su código.

  • una construcción foreach(item in list) le permite utilizar break o continue si tiene que salir de la iteración o el bucle. Pero no puedes alterar la lista dentro de un ciclo foreach.

Me sorprende ver que list.ForEach es un poco más rápido. Pero esa no es una razón válida para usarla en todo momento, sería una optimización prematura. Si su aplicación utiliza una base de datos o servicio web que, no control de bucle, casi siempre va a estar donde el tiempo lo permita. ¿Y lo ha comparado con un lazo for también? El list.ForEach podría ser más rápido debido al uso interno y un bucle for sin el envoltorio sería aún más rápido.

No estoy de acuerdo con que la versión list.ForEach(delegate) sea "más funcional" de manera significativa. Transfiere una función a una función, pero no hay una gran diferencia en el resultado o la organización del programa.

No creo que foreach(item in list) "dice exactamente cómo quiere que se haga" - un bucle for(int 1 = 0; i < count; i++) hace eso, un bucle foreach deja la elección del control hasta el compilador.

Mi sensación es, en un nuevo proyecto, para usar foreach(item in list) para la mayoría de los bucles con el fin de cumplir con el uso común y para facilitar la lectura, y el uso de list.Foreach() sólo para bloques cortos, cuando se puede hacer algo más elegante o de forma compacta con el C# 3 "=>" operador. En casos como ese, es posible que ya exista un método de extensión LINQ que sea más específico que ForEach(). Vea si Where(), Select(), Any(), All(), Max() o uno de los muchos otros métodos LINQ ya no hace lo que desea del ciclo.

+6

No tengo idea de por qué era un -1 porque considero que es una respuesta excelente. –

+0

Solo por curiosidad ... mire la implementación de Microsoft ... http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,0e5a9cf0a310b9e5 – Alexandre

11

Conozco dos cosas oscuras que las hacen diferentes. Veme!

En primer lugar, existe el error clásico de crear un delegado para cada elemento de la lista. Si utiliza la palabra clave foreach, todos los delegados pueden terminar en referencia al último elemento de la lista:

// A list of actions to execute later 
    List<Action> actions = new List<Action>(); 

    // Numbers 0 to 9 
    List<int> numbers = Enumerable.Range(0, 10).ToList(); 

    // Store an action that prints each number (WRONG!) 
    foreach (int number in numbers) 
     actions.Add(() => Console.WriteLine(number)); 

    // Run the actions, we actually print 10 copies of "9" 
    foreach (Action action in actions) 
     action(); 

    // So try again 
    actions.Clear(); 

    // Store an action that prints each number (RIGHT!) 
    numbers.ForEach(number => 
     actions.Add(() => Console.WriteLine(number))); 

    // Run the actions 
    foreach (Action action in actions) 
     action(); 

El método List.ForEach no tiene este problema. El elemento actual de la iteración se pasa por valor como un argumento al lambda externo, y luego el lambda interno captura correctamente ese argumento en su propio cierre. Problema resuelto.

(Lamentablemente, creo que ForEach es miembro de List, en lugar de un método de extensión, aunque es fácil de definirlo usted mismo, por lo que tiene esta función en cualquier tipo enumerable.)

En segundo lugar, el enfoque del método ForEach tiene una limitación. Si está implementando IEnumerable mediante el retorno de rendimiento, no puede hacer un retorno de rendimiento dentro de la lambda. Por lo tanto, este método no permite pasar por los elementos de una colección para poder devolver cosas. Deberá usar la palabra clave foreach y solucionar el problema de cierre haciendo manualmente una copia del valor del ciclo actual dentro del ciclo.

More here

+4

El problema que menciona con 'foreach' está" arreglado "en C# 5. http://stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the-variable-in-a-foreach – StriplingWarrior

1

La función ParaCada es miembro de la lista de clase genérica.

He creado la siguiente extensión para reproducir el código interno:

public static class MyExtension<T> 
    { 
     public static void MyForEach(this IEnumerable<T> collection, Action<T> action) 
     { 
      foreach (T item in collection) 
       action.Invoke(item); 
     } 
    } 

lo tanto, un final estamos utilizando un foreach normal (o un bucle de si lo desea).

Por otro lado, el uso de una función de delegado es sólo otra manera de definir una función, este código:

delegate(string s) { 
    <process the string> 
} 

es equivalente a:

private static void myFunction(string s, <other variables...>) 
{ 
    <process the string> 
} 

o utilizando expresiones LABDA:

(s) => <process the string> 
1

Todo el ámbito ForEach (función de delegado) se trata como una sola línea de código (llamando al unction), y no puede establecer puntos de interrupción ni entrar en el código. Si ocurre una excepción no controlada, se marca todo el bloque.

+0

que depende completamente de la forma en que codificar la función de delegado ... – Aamir