Aparentemente, hay muchas formas de iterar sobre una colección. Curioso si hay alguna diferencia, o por qué usarías una forma sobre la otra.
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, en lugar del delegado anónimo que uso arriba, tendrías un delegado reutilizable que podrías especificar ...
Respuestas:
Hay una distinción importante y útil entre los dos.
Debido a que .ForEach usa un
for
bucle para iterar la colección, esto es válido (edición: antes de .net 4.5 - la implementación cambió y ambos arrojan):mientras que
foreach
usa un enumerador, por lo que esto no es válido:tl; dr: ¡NO copiar este código en su aplicación!
Estos ejemplos no son las mejores prácticas, son solo para demostrar las diferencias entre
ForEach()
yforeach
.Eliminar elementos de una lista dentro de un
for
ciclo puede tener efectos secundarios. El más común se describe en los comentarios a esta pregunta.En general, si desea eliminar varios elementos de una lista, desearía separar la determinación de qué elementos eliminar de la eliminación real. No mantiene su código compacto, pero garantiza que no se perderá ningún artículo.
fuente
Teníamos un código aquí (en VS2005 y C # 2.0) donde los ingenieros anteriores se esforzaron por usar en
list.ForEach( delegate(item) { foo;});
lugar deforeach(item in list) {foo; };
todo el código que escribieron. por ejemplo, un bloque de código para leer filas de un dataReader.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
=>
sintaxis " " para hacer algunas expresiones bien concisas.Es menos familiar. Las personas que tienen que mantener este código se preguntarán por qué lo hicieron de esa manera. Me tomó un tiempo decidir que no había ninguna razón, excepto quizás hacer que el escritor pareciera inteligente (la calidad del resto del código lo socavaba). También era menos legible, con el "
})
" al final del bloque de código de delegado.Vea también el libro de Bill Wagner "C # efectivo: 50 formas específicas de mejorar su C #" donde habla sobre por qué se prefiere foreach a otros bucles como for o while, el punto principal es que está dejando que el compilador decida la mejor manera de construir el lazo. Si una versión futura del compilador logra utilizar una técnica más rápida, obtendrá esto de forma gratuita mediante el uso de foreach y la reconstrucción, en lugar de cambiar su código.
una
foreach(item in list)
construcción le permite usarbreak
ocontinue
si necesita salir de la iteración o del bucle. Pero no puede alterar la lista dentro de un bucle foreach.Me sorprende ver que
list.ForEach
es un poco más rápido. Pero probablemente esa no sea una razón válida para usarlo, sería una optimización prematura. Si su aplicación utiliza una base de datos o un servicio web que, no el control de bucle, casi siempre estará donde sea que pase el tiempo. ¿Y también lo has comparado con unfor
bucle? Ellist.ForEach
podría ser más rápido debido al uso interno yfor
bucle sin el envoltorio sería aún más rápido.No estoy de acuerdo con que el
list.ForEach(delegate)
versión 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)
"diga exactamente cómo quieres que se haga": unfor(int 1 = 0; i < count; i++)
bucle hace eso, unforeach
bucle deja la elección del control al compilador.Mi intención es, en un nuevo proyecto, utilizar la
foreach(item in list)
mayoría de los bucles para cumplir con el uso común y facilitar la lectura, y usarlist.Foreach()
solo para bloques cortos, cuando puede hacer algo de manera más elegante o compacta con el=>
operador C # 3 " ". En casos como ese, puede haber un método de extensión LINQ que sea más específico queForEach()
. A ver siWhere()
,Select()
,Any()
,All()
,Max()
o uno de los muchos otros métodos de LINQ no ya hacer lo que desee en el bucle.fuente
Por diversión, metí List en reflector y este es el C # resultante:
Del mismo modo, MoveNext en Enumerator, que es lo que usa foreach es este:
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 enumerador sin importar qué. El GC es tu amigo, pero si estás haciendo el mismo foreach repetidamente, esto generará más objetos descartables, en lugar de reutilizar el mismo delegado, PERO , este es realmente un caso marginal. En el uso típico, verá poca o ninguna diferencia.
fuente
Sé dos cosas oscuras que las hacen diferentes. Ve a mi
En primer lugar, está el error clásico de hacer un delegado para cada elemento de la lista. Si usa la palabra clave foreach, todos sus delegados pueden terminar refiriéndose al último elemento de la lista:
El método List.ForEach no tiene este problema. El elemento actual de la iteración se pasa por valor como argumento a la lambda externa, y luego la lambda interna 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 para que tenga esta facilidad en cualquier tipo enumerable).
En segundo lugar, el enfoque del método ForEach tiene una limitación. Si está implementando IEnumerable mediante el rendimiento de rendimiento, no puede hacer un rendimiento dentro de la lambda. Por lo tanto, este método no permite recorrer los elementos de una colección para obtener resultados. Tendrá que usar la palabra clave foreach y evitar el problema de cierre haciendo manualmente una copia del valor del bucle actual dentro del bucle.
Más aquí
fuente
foreach
está "solucionado" en C # 5. stackoverflow.com/questions/8898925/…Supongo que la
someList.ForEach()
llamada podría paralelizarse fácilmente, mientras que lo normalforeach
no es tan fácil de ejecutar en paralelo. Podría ejecutar fácilmente varios delegados diferentes en diferentes núcleos, lo que no es tan fácil de hacer con un normalforeach
.Solo mis 2 centavos
fuente
Como dicen, el diablo está en los detalles ...
La mayor diferencia entre los dos métodos de enumeración de colecciones es que
foreach
lleva el estado, mientrasForEach(x => { })
que no.Pero profundicemos un poco más, porque hay algunas cosas que debe tener en cuenta que pueden influir en su decisión, y hay algunas advertencias que debe tener en cuenta al codificar para cualquier caso.
Usemos
List<T>
en nuestro pequeño experimento para observar el comportamiento. Para este experimento, estoy usando .NET 4.7.2:Vamos a iterar sobre esto con
foreach
primero:Podríamos ampliar esto en:
Con el enumerador en la mano, mirando debajo de las cubiertas obtenemos:
Dos cosas se hacen evidentes de inmediato:
Por supuesto, esto no es seguro para subprocesos. Como se señaló anteriormente, cambiar la colección mientras se itera es un mal mojo.
Pero, ¿qué pasa con el problema de que la colección se vuelva inválida durante la iteración por medios ajenos a la manipulación de la colección durante la iteración? Las mejores prácticas sugieren versionar la colección durante las operaciones y la iteración, y verificar versiones para detectar cuándo cambia la colección subyacente.
Aquí es donde las cosas se ponen realmente turbias. De acuerdo con la documentación de Microsoft:
Bueno, qué significa eso? A modo de ejemplo, solo porque
List<T>
implementa el manejo de excepciones no significa que todas las colecciones que implementanIList<T>
harán lo mismo. Eso parece ser una clara violación del Principio de sustitución de Liskov:Otro problema es que el enumerador debe implementar
IDisposable
, lo que significa otra fuente de posibles pérdidas de memoria, no solo si la persona que llama se equivoca, sino si el autor no implementaDispose
patrón correctamente.Por último, tenemos un problema de por vida ... ¿qué sucede si el iterador es válido, pero la colección subyacente se ha ido? Ahora tenemos una instantánea de lo que era ... cuando separas la vida útil de una colección y sus iteradores, estás pidiendo problemas.
Ahora examinemos
ForEach(x => { })
:Esto se expande a:
De nota importante es la siguiente:
for (int index = 0; index < this._size && ... ; ++index) action(this._items[index]);
Este código no asigna ningún enumerador (nada a
Dispose
), y no se detiene mientras itera.Tenga en cuenta que esto también realiza una copia superficial de la colección subyacente, pero la colección ahora es una instantánea en el tiempo. Si el autor no implementa correctamente una comprobación para que la colección cambie o quede "obsoleta", la instantánea sigue siendo válida.
Esto de ninguna manera lo protege del problema de los problemas de por vida ... si la colección subyacente desaparece, ahora tiene una copia superficial que señala lo que fue ... pero al menos no tiene un
Dispose
problema para tratar con iteradores huérfanos ...Sí, dije iteradores ... a veces es ventajoso tener estado. Suponga que desea mantener algo similar a un cursor de base de datos ... tal vez el
foreach
estiloIterator<T>
a seguir sea el de varios estilos . Personalmente, no me gusta este estilo de diseño, ya que hay demasiados problemas de por vida, y confías en las buenas gracias de los autores de las colecciones en las que confías (a menos que literalmente escribas todo tú mismo desde cero).Siempre hay una tercera opción ...
No es sexy, pero tiene dientes (disculpas a Tom Cruise y la película The Firm )
Es su elección, pero ahora lo sabe y puede ser informado.
fuente
Podrías nombrar al delegado anónimo :-)
Y puedes escribir el segundo como:
Lo cual prefiero, y ahorra mucho tipeo.
Como dice Joachim, el paralelismo es más fácil de aplicar a la segunda forma.
fuente
Detrás de escena, el delegado anónimo se convierte en un método real para que pueda tener algunos gastos generales con la segunda opción si el compilador no eligió incorporar la función. Además, cualquier variable local referenciada por el cuerpo del ejemplo de delegado anónimo cambiaría de 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
fuente
La función ForEach es miembro de la clase genérica Lista.
He creado la siguiente extensión para reproducir el código interno:
Entonces, al final, estamos usando un foreach normal (o un bucle si lo desea).
Por otro lado, usar una función delegada es solo otra forma de definir una función, este código:
es equivalente a:
o usando expresiones labda:
fuente
Todo el alcance de ForEach (función delegada) 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 se produce una excepción no controlada, se marca todo el bloque.
fuente
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 quieres que se haga. Esto dejaList.ForEach
libre para cambiar la implementación de la parte de cómo en el futuro. Por ejemplo, una futura versión hipotética de .Net siempre podría ejecutarseList.ForEach
en paralelo, bajo el supuesto de que en este punto todos tienen varios núcleos de CPU que generalmente están inactivos.Por otro lado,
foreach (item in list)
te da un poco más de control sobre el ciclo. Por ejemplo, sabe que los elementos se iterarán en algún tipo de orden secuencial, y podría dividirse fácilmente en el medio si un elemento cumple alguna condición.Algunos comentarios más recientes sobre este tema están disponibles aquí:
fuente
La segunda forma que mostró utiliza un método de extensión para ejecutar el método delegado para cada uno de los elementos de la lista.
De esta manera, tiene otra llamada de delegado (= método).
Además, existe la posibilidad de iterar la lista con un bucle for .
fuente
Una cosa a tener en cuenta es cómo salir del método genérico .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é: pensarías que serían equivalentes una vez compilados ...
fuente
Hay una manera, lo he hecho en mi aplicación:
Puedes usarlo como tu artículo desde el foreach
fuente