foreach vs someList.ForEach () {}

Aparentemente hay muchas maneras de iterar sobre una colección. Curioso si hay alguna diferencia, o por qué usaría una forma sobre la otra.

Primer tipo:

List someList =  foreach(string s in someList) {  } 

Otra manera:

 List someList =  someList.ForEach(delegate(string s) {  }); 

Supongo que, en lo alto de mi cabeza, en vez del delegado anónimo que uso arriba, tendrías un delegado reutilizable que podrías especificar …

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

Debido a que .ForEach usa un ciclo for para iterar la colección, esto es válido (edit: antes de .net 4.5 – la implementación cambió y ambos lanzan):

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

mientras que foreach usa un enumerador, entonces esto no es válido:

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

tl; dr: ¡NO copie este código en su aplicación!

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

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

En general, si está buscando eliminar varios elementos de una lista, le conviene 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.

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

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

Los inconvenientes de list.ForEach() son:

  • Es más detallado en C # 2.0. Sin embargo, en C # 3 en adelante, puede usar la syntax ” => ” 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 rest 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 loops – el punto principal es que está dejando que el comstackdor decida la mejor manera de construir el lazo. Si una versión futura del comstackdor 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 de foreach(item in list) permite utilizar la break o continue si necesita salir de la iteración o del ciclo. Pero no puedes alterar la lista dentro de un ciclo foreach.

Me sorprende ver esa lista. 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 has comparado con un bucle for también? La list.ForEach podría ser más rápido debido al uso interno y un bucle for sin el wrapper sería aún más rápido.

No estoy de acuerdo con que la list.ForEach(delegate) versión list.ForEach(delegate) es “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 progtwig.

No creo que foreach(item in list) “diga exactamente cómo lo quiere hacer” – un ciclo for(int 1 = 0; i < count; i++) hace eso, un bucle foreach deja la opción de control hasta el comstackdor

Tengo la sensación, en un proyecto nuevo, de usar foreach(item in list) para la mayoría de los bucles para cumplir con el uso común y para la legibilidad, y use list.Foreach() solo para bloques cortos, cuando puede hacer algo más elegantemente o compactamente con el operador C # 3 " => ". En casos como ese, puede 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.

Para divertirme, introduje List en el reflector y este es el C # resultante:

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

De forma similar, MoveNext en Enumerator, que es lo que utiliza foreach, es este:

 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) lo más probable es que JIT se convierta en algo eficiente.

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

Conozco dos cosas oscuras que los hacen diferentes. Veme!

En primer lugar, está el error clásico de crear un delegado para cada elemento de la lista. Si usa la palabra clave foreach, todos sus delegates pueden terminar refiriéndose al último elemento de la lista:

  // A list of actions to execute later List actions = new List(); // Numbers 0 to 9 List 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 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.

Más aquí

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. Podría ejecutar fácilmente varios delegates diferentes en núcleos diferentes, lo cual no es tan fácil de hacer con un foreach normal.
Solo mis 2 centavos

Puedes nombrar al delegado anónimo 🙂

Y puedes escribir el segundo como:

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

Lo cual prefiero, y ahorra un montón de tipeo.

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

List.ForEach () se considera 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 a List.ForEach libre de cambiar la implementación del cómo en el futuro. Por ejemplo, una versión futura hipotética 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.

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 comstackdor 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 comstackdor para ocultar el hecho de que se comstack 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

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 ciclo for .

Una cosa de qué preocuparse es cómo salir del método Generic .ForEach: vea esta discusión . Aunque el enlace parece decir que de esta manera es el más rápido. No estoy seguro de por qué, uno pensaría que serían equivalentes una vez comstackdos …

La función ForEach es miembro de la clase genérica List.

Creé la siguiente extensión para reproducir el código interno:

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

Así que al final estamos usando un foreach normal (o un bucle para si lo desea).

Por otro lado, usar una función de delegado es solo otra manera de definir una función, este código:

 delegate(string s) {  } 

es equivalente a:

 private static void myFunction(string s, ) {  } 

o usando expresiones labda:

 (s) =>  

El scope completo de ForEach (función de delegado) se trata como una sola línea de código (que llama a la función), y no puede establecer puntos de interrupción o ingresar al código. Si ocurre una excepción no controlada, se marca todo el bloque.