¿Por qué debería usar foreach en lugar de for (int i = 0; i <length; i ++) en loops?

Parece que la forma genial de hacer bucles en C # y Java es usar foreach en lugar del estilo C para bucles.

¿Hay alguna razón por la que prefiera este estilo sobre el estilo C?

Estoy particularmente interesado en estos dos casos, pero por favor diríjase a todos los casos que necesite para explicar sus puntos.

  • Deseo realizar una operación en cada elemento de una lista.
  • Estoy buscando un artículo en una lista y deseo salir cuando se encuentre ese artículo.

Dos razones principales que puedo pensar son:

1) Se abstrae del tipo de contenedor subyacente. Esto significa, por ejemplo, que no tiene que cambiar el código que recorre todos los elementos del contenedor cuando cambia el contenedor; está especificando el objective de “hacer esto para cada elemento en el contenedor”, no los medios .

2) Elimina la posibilidad de errores “uno por uno”.

En términos de realizar una operación en cada elemento de una lista, es intuitivo decir simplemente:

for(Item item: lst) { op(item); } 

Expresa perfectamente la intención del lector, en lugar de hacer cosas manualmente con iteradores. Lo mismo para buscar elementos.

Imagina que eres el jefe de cocina de un restaurante y preparas una enorme tortilla para un buffet. Usted entrega una caja de cartón con una docena de huevos a cada uno de los dos empleados de la cocina y les dice que se rompan, literalmente.

El primero establece un cuenco, abre la caja, agarra cada huevo por turno – de izquierda a derecha a través de la fila superior, luego la fila inferior – rompiéndolo contra el costado del cuenco y luego vaciándolo en el cuenco. Finalmente se queda sin huevos. Un trabajo bien hecho.

El segundo establece un cuenco, abre la caja y luego se marcha para coger un trozo de papel y un bolígrafo. Escribe los números del 0 al 11 junto a los compartimentos de la caja de huevos y el número 0 en el papel. Mira el número en el papel, encuentra el compartimento etiquetado como 0, saca el huevo y lo parte en el cuenco. Mira nuevamente el 0 en el papel, piensa “0 + 1 = 1”, tacha el 0 y escribe 1 en el papel. Él agarra el huevo del compartimento 1 y lo rompe. Y así sucesivamente, hasta que el número 12 aparezca en el papel y él sepa (¡sin mirar!) Que ya no quedan más huevos. Un trabajo bien hecho.

Uno pensaría que el segundo tipo estaba un poco en la cabeza, ¿verdad?

El objective de trabajar en un lenguaje de alto nivel es evitar tener que describir las cosas en términos de computadora y poder describirlas en sus propios términos. Cuanto más alto sea el idioma, más verdadero es esto. Incrementar un contador en un bucle es una distracción de lo que realmente quiere hacer: procesar cada elemento.


Además de eso, las estructuras del tipo de lista enlazada no se pueden procesar de manera eficiente al incrementar un contador y al indexar en: “indexación” significa comenzar el conteo desde el principio. En C, podemos procesar una lista vinculada que hicimos nosotros mismos mediante el uso de un puntero para el “contador” del ciclo y desreferenciarlo. Podemos hacer esto en C ++ moderno (y hasta cierto punto en C # y Java) usando “iteradores”, pero esto todavía sufre del problema de indirección.


Finalmente, algunos idiomas tienen un nivel lo suficientemente alto como para que la idea de escribir un ciclo para “realizar una operación en cada elemento de una lista” o “buscar un elemento en una lista” sea espantosa (de la misma manera que el chef jefe no debería tener que decirle al primer miembro del personal de cocina cómo asegurarse de que todos los huevos estén rajados). Se proporcionan funciones que configuran esa estructura de bucle, y usted les dice, a través de una función de orden superior, o quizás un valor de comparación, en el caso de búsqueda, qué hacer dentro del bucle. (De hecho, puede hacer estas cosas en C ++, aunque las interfaces son algo torpes).

  • foreach es más simple y más legible

  • Puede ser más eficiente para construcciones como listas vinculadas

  • No todas las colecciones admiten acceso aleatorio; la única forma de iterar un HashSet o un Dictionary.KeysCollection es para todos.

  • foreach permite iterar a través de una colección devuelta por un método sin una variable temporal adicional:

     foreach(var thingy in SomeMethodCall(arguments)) { ... } 

Un beneficio para mí es que es menos fácil cometer errores como

  for(int i = 0; i < maxi; i++) { for(int j = 0; j < maxj; i++) { ... } } 

ACTUALIZACIÓN: esta es una forma en que ocurre el error. Hago una sum

 int sum = 0; for(int i = 0; i < maxi; i++) { sum += a[i]; } 

y luego decide agregarlo más. Así que envuelvo el ciclo en otro.

 int total = 0; for(int i = 0; i < maxi; i++) { int sum = 0; for(int i = 0; i < maxi; i++) { sum += a[i]; } total += sum; } 

La comstackción falla, por supuesto, así que editamos a mano

 int total = 0; for(int i = 0; i < maxi; i++) { int sum = 0; for(int j = 0; j < maxj; i++) { sum += a[i]; } total += sum; } 

Ahora hay al menos DOS errores en el código (y más si hemos confundido maxi y maxj) que solo serán detectados por los errores de tiempo de ejecución. Y si no escribes pruebas ... y es una pieza de código rara - esto morderá a alguien más - mal.

Es por eso que es una buena idea extraer el bucle interno en un método:

 int total = 0; for(int i = 0; i < maxi; i++) { total += totalTime(maxj); } private int totalTime(int maxi) { int sum = 0; for(int i = 0; i < maxi; i++) { sum += a[i]; } return sum; } 

y es más legible

foreach funcionará de forma idéntica a for en todos los escenarios [1], incluidos los sencillos como usted describe.

Sin embargo, foreach tiene ciertas ventajas no relacionadas con el rendimiento sobre:

  1. Conveniencia . No necesita mantener un local i adicional (que no tiene otro propósito en la vida que no sea facilitar el ciclo), y no necesita obtener el valor actual en una variable usted mismo; la construcción del bucle ya se ha ocupado de eso.
  2. Consistencia . Con foreach , puede iterar sobre secuencias que no son matrices con la misma facilidad. Si desea usar for para recorrer una secuencia no ordenada (por ejemplo, un mapa / diccionario), debe escribir el código de forma un poco diferente. foreach es el mismo en todos los casos que cubre.
  3. Seguridad Con un gran poder viene una gran responsabilidad. ¿Por qué abrir oportunidades para errores relacionados con el incremento de la variable de ciclo si no la necesita en primer lugar?

Entonces, como vemos, foreach es “mejor” de usar en la mayoría de las situaciones.

Dicho esto, si necesita el valor de i para otros fines, o si está manejando una estructura de datos que sabe que es una matriz (y hay una razón específica real para que sea una matriz), la mayor funcionalidad que la más abajo -to-the-metal for ofertas será el camino a seguir.

[1] “En todos los escenarios” realmente significa “todos los escenarios donde la colección es amigable para ser iterada”, lo que en realidad sería “la mayoría de los escenarios” (ver comentarios a continuación). Realmente creo que un escenario de iteración que involucre una colección hostil a la iteración tendría que ser diseñado, sin embargo.

Probablemente también deba considerar LINQ si se dirige a C # como idioma, ya que esta es otra forma lógica de hacer bucles.

Al realizar una operación en cada elemento de una lista, ¿quiere decir modificarlo en su lugar en la lista, o simplemente hacer algo con el elemento (por ejemplo, imprimirlo, acumularlo, modificarlo, etc.)? Sospecho que es el último, ya que foreach en C # no le permitirá modificar la colección sobre la que está pasando, o al menos no de una manera conveniente …

Aquí hay dos construcciones simples, primero usando for y luego usando foreach , que visitan todas las cadenas en una lista y las convierten en cadenas en mayúsculas:

 List list = ...; List uppercase = new List (); for (int i = 0; i < list.Count; i++) { string name = list[i]; uppercase.Add (name.ToUpper ()); } 

(Tenga en cuenta que el uso de la condición final i < list.Count vez de i < length con alguna constante de length precomputadora se considera una buena práctica en .NET, ya que el comstackdor tendría que verificar el límite superior cuando se invoque la list[i] en el circuito, si mi comprensión es correcta, el comstackdor puede, en algunas circunstancias, optimizar la verificación del límite superior que normalmente habría hecho).

Aquí está el equivalente foreach :

 List list = ...; List uppercase = new List (); foreach (name in list) { uppercase.Add (name.ToUpper ()); } 

Nota: básicamente, el constructo foreach puede iterar sobre cualquier IEnumerable o IEnumerable en C #, no solo sobre arreglos o listas. Por lo tanto, es posible que la cantidad de elementos en la colección no se conozca de antemano, o incluso que sea infinita (en cuyo caso, sin duda deberá incluir alguna condición de terminación en su ciclo, o no se cerrará).

Aquí hay algunas soluciones equivalentes que se me ocurren, expresadas usando C # LINQ (y que introduce el concepto de una expresión lambda , básicamente una función en línea que toma una x y devuelve x.ToUpper () en los siguientes ejemplos):

 List list = ...; List uppercase = new List (); uppercase.AddRange (list.Select (x => x.ToUpper ())); 

O con la lista en uppercase poblada por su constructor:

 List list = ...; List uppercase = new List (list.Select (x => x.ToUpper ())); 

O lo mismo usando la función ToList :

 List list = ...; List uppercase = list.Select (x => x.ToUpper ()).ToList (); 

O aún lo mismo con la inferencia de tipo:

 List list = ...; var uppercase = list.Select (x => x.ToUpper ()).ToList (); 

o si no le molesta obtener el resultado como una IEnumerable (una colección de cadenas enumerable), podría soltar la lista ToList :

 List list = ...; var uppercase = list.Select (x => x.ToUpper ()); 

O tal vez otro con el estilo de C # SQL y palabras clave select , que es totalmente equivalente:

 List list = ...; var uppercase = from name in list select name => name.ToUpper (); 

LINQ es muy expresivo y muy a menudo, creo que el código es más legible que un ciclo simple.

Su segunda pregunta, la búsqueda de un elemento en una lista, y el deseo de salir cuando se encuentra ese elemento también se puede implementar de forma muy conveniente utilizando LINQ. Aquí hay un ejemplo de un bucle foreach :

 List list = ...; string result = null; foreach (name in list) { if (name.Contains ("Pierre")) { result = name; break; } } 

Aquí está el equivalente directo de LINQ:

 List list = ...; string result = list.Where (x => x.Contains ("Pierre")).FirstOrDefault (); 

o con la syntax de consulta:

 List list = ...; var results = from name in list where name.Contains ("Pierre") select name; string result = results.FirstOrDefault (); 

La enumeración de results solo se ejecuta bajo demanda , lo que significa que efectivamente, la lista solo se repetirá hasta que se cumpla la condición, cuando se invoque el método FirstOrDefault .

Espero que esto aporte un poco más de contexto al debate for o foreach , al menos en el mundo de .NET.

Como respondió Stuart Golodetz, es una abstracción.

Si solo está utilizando i como índice, en lugar de usar el valor de i para otro fin, como

  String[] lines = getLines(); for( int i = 0 ; i < 10 ; ++i ) { System.out.println( "line " + i + lines[i] ) ; } 

entonces no hay necesidad de conocer el valor actual de i , y ser capaz de generar la posibilidad de errores:

  Line[] pages = getPages(); for( int i = 0 ; i < 10 ; ++i ) { for( int j = 0 ; j < 10 ; ++i ) System.out.println( "page " + i + "line " + j + page[i].getLines()[j]; } 

Como dice Andrew Koenig, "la abstracción es ignorancia selectiva"; si no necesita conocer los detalles de cómo iterar alguna colección, busque una forma de ignorar esos detalles y escriba un código más sólido.

Razones para usar foreach:

  • Evita que ingresen errores (por ejemplo, se olvidó de i++ en el bucle for) que podría causar un mal funcionamiento del bucle. Hay muchas formas de arruinar los bucles, pero no muchas formas de arruinar los bucles foreach.
  • Se ve mucho más limpio / menos críptico.
  • Un bucle for puede incluso no ser posible en algunos casos (por ejemplo, si tiene un IEnumerable , que no puede indexarse ​​como un IList puede).

Razones para usar for :

  • Este tipo de bucles tiene una ligera ventaja de rendimiento cuando se itera sobre listas planas (matrices) porque no hay un nivel adicional de indirección creado mediante el uso de un enumerador. (Sin embargo, esta ganancia de rendimiento es mínima).
  • El objeto que desea enumerar no implementa IEnumerableforeach solo opera en enumerables.
  • Otras situaciones especializadas por ejemplo, si está copiando de una matriz a otra, foreach no le dará una variable de índice que pueda usar para direccionar la ranura de la matriz de destino. for es casi lo único que tiene sentido en tales casos.

Los dos casos que enumera en su pregunta son efectivamente idénticos cuando usa cualquiera de los dos: en el primero, simplemente itera hasta el final de la lista, y en el segundo se break; una vez que haya encontrado el artículo que está buscando.

Solo para explicar más por foreach , este ciclo:

 IEnumerable bar = ...; foreach (var foo in bar) { // do stuff } 

es azúcar sintáctica para:

 IEnumerable bar = ...; IEnumerator e = bar.GetEnumerator(); try { Something foo; while (e.MoveNext()) { foo = e.Current; // do stuff } } finally { ((IDisposable)e).Dispose(); } 

Si está iterando sobre una colección que implementa IEnumerable, es más natural usar foreach porque el siguiente miembro de la iteración se asigna al mismo tiempo que se realiza la prueba para llegar al final. P.ej,

  foreach (string day in week) {/* Do something with the day ... */} 

es más directo que

 for (int i = 0; i < week.Length; i++) { day = week[i]; /* Use day ... */ } 

También puede usar un ciclo for en la implementación propia de su clase de IEnumerable. Simplemente haga que su implementación de GetEnumerator () use la palabra clave de cedencia C # en el cuerpo de su ciclo:

 yield return my_array[i]; 

Java tiene los dos tipos de bucle que ha señalado. Puede usar cualquiera de las variantes de bucle for dependiendo de su necesidad. Tu necesidad puede ser así

  1. Desea volver a ejecutar el índice de su elemento de búsqueda en la lista.
  2. Quieres obtener el artículo en sí mismo.

En el primer caso, debe usar el estilo clásico (estilo c) para el ciclo. pero en el segundo caso debe usar el ciclo foreach .

El bucle foreach se puede usar en el primer caso. pero en ese caso necesita mantener su propio índice.

Si puede hacer lo que necesita con foreach , foreach ; si no, por ejemplo, si necesita la variable de índice por alguna razón, utilícela. ¡Sencillo!

(Y sus dos escenarios son igualmente posibles con o for foreach ).

Una razón para no utilizar Foreach al menos en Java es que creará un objeto iterador que eventualmente será recolectado. Por lo tanto, si intenta escribir código que evite la recolección de basura, es mejor evitar foreach. Sin embargo, creo que está bien para arrays puros porque no crea un iterador.

Podría pensar en varios motivos

  1. no se can't mess up indexes , también en el entorno móvil no se tienen optimizaciones del comstackdor y se ha escrito pésimamente para que el bucle pueda hacer varias comprobaciones de bounderay, mientras que para cada bucle solo hace 1.
  2. no can't change data input size (agregar / eliminar elementos) mientras lo itera. Tu código no frena tan fácilmente. Si necesita filtrar o transformar datos, utilice otros bucles.
  3. puede iterar sobre estructuras de datos, que no pueden accederse por índice, pero pueden rastrearse. Para cada uno solo necesita que implemente una iterable interface (java) o extienda IEnumerable (c #).
  4. puede tener una boiler plate más pequeña, por ejemplo, al analizar XML su diferencia entre SAX y StAX, primero necesita una copia en memoria del DOM para referirse a un elemento que itera sobre los datos (no es tan rápido, pero es eficiente desde el punto de vista de la memoria) )

Tenga en cuenta que si está buscando un elemento en la lista para cada uno, lo más probable es que lo esté haciendo mal. Considere usar hashmap o bimap para omitir la búsqueda todos juntos.

Suponiendo que el progtwigdor quiera usar for loop como for each iterador que utiliza, existe un error común de omisión de elementos. Entonces en esa escena es más seguro.

 for ( Iterator elements = input.iterator(); elements.hasNext(); ) { // Inside here, nothing stops programmer from calling `element.next();` // more then once. } 

Hablando de código limpio, ¡una statement foreach es mucho más rápida de leer que una statement for!

Linq (en C #) puede hacer más o menos lo mismo, ¡pero los desarrolladores novatos tienden a tener dificultades para leerlos!

Parece que la mayoría de los artículos están cubiertos … las siguientes son algunas notas adicionales que no veo mencionadas con respecto a sus preguntas específicas. Estas son reglas duras en lugar de preferencias de estilo de una forma u otra:

Deseo realizar una operación en cada elemento de una lista

En un bucle foreach , no puede cambiar el valor de la variable de iteración, por lo que si está buscando cambiar el valor de un elemento específico en su lista, debe usarlo.

También vale la pena señalar que la forma “genial” ahora es usar LINQ; hay muchos recursos que puede buscar si está interesado.

foreach es de un orden de magnitud más lento para una implementación pesada de la colección.

Tengo pruebas. Estos son mis hallazgos

Utilicé el siguiente generador de perfiles simple para probar su rendimiento

 static void Main(string[] args) { DateTime start = DateTime.Now; List names = new List(); Enumerable.Range(1, 1000).ToList().ForEach(c => names.Add("Name = " + c.ToString())); for (int i = 0; i < 100; i++) { //For the for loop. Uncomment the other when you want to profile foreach loop //and comment this one //for (int j = 0; j < names.Count; j++) // Console.WriteLine(names[j]); //for the foreach loop foreach (string n in names) { Console.WriteLine(n); } } DateTime end = DateTime.Now; Console.WriteLine("Time taken = " + end.Subtract(start).TotalMilliseconds + " milli seconds"); 

Y obtuve los siguientes resultados

Tiempo transcurrido = 11320.73 milisegundos (para el ciclo)

Tiempo tomado = 11742.3296 mili segundos (ciclo foreach)

Un foreach también le notifica si la colección que está enumerando cambia (es decir, usted TENÍA 7 elementos en su colección … hasta que otra operación en un hilo separado eliminó uno y ahora usted solo tiene 6 @ _ @)

Solo quería agregar que quienquiera que piense que foreach se traduce y, por lo tanto, no tiene diferencia en el rendimiento, está totalmente equivocado. Hay muchas cosas que ocurren bajo el capó, es decir, la enumeración del tipo de objeto que NO está en un bucle simple. Se parece más a un bucle de iterador:

 Iterator iter = o.getIterator(); while (iter.hasNext()){ obj value = iter.next(); ...do something } 

que es significativamente diferente de un bucle simple. Si no entiendes por qué, entonces busca tablas. Además, ¿quién sabe cuál es la función hasNext? Por todo lo que sabemos, podría ser:

 while (!haveiwastedtheprogramstimeenough){ } now advance 

Exageración aparte, hay una función de implementación y eficiencia desconocida que se llama. Debido a que los comstackdores no optimizan a través de los límites de las funciones, NO hay optimización que suceda aquí, solo su simple búsqueda vtable y función de llamada. Esto no es solo teoría, en la práctica, he visto aceleraciones significativas al cambiar de foreach a for en el estándar C # ArrayList. Para ser justos, era una lista de arrays con aproximadamente 30,000 artículos, pero aún así.