C # foreach mejoras?

9

Me encuentro con esto a menudo durante la programación donde quiero tener un índice de conteo de bucles dentro de un foreach y tengo que crear un número entero, usarlo, incrementar, etc. ¿No sería una buena idea si se introdujera una palabra clave que fuera el bucle cuenta dentro de un foreach? También podría usarse en otros bucles también.

¿Esto va en contra del uso de palabras clave, ya que el compilador no permitiría que esta palabra clave se usara en otro lugar sino en una construcción de bucle?

Justin
fuente
77
Puede hacerlo usted mismo con un método de extensión, vea la respuesta de Dan Finch: stackoverflow.com/a/521894/866172 (no es la respuesta mejor calificada, pero creo que es la "más limpia")
Jalayn
Perl y cosas como $ _ vienen a la mente. Odio esas cosas
Yam Marcovic
2
Por lo que sé sobre C #, tiene muchas palabras clave basadas en el contexto, por lo que esto "no iría en contra del uso de palabras clave". Sin embargo, como se señala en una respuesta a continuación, probablemente solo desee usar un for () bucle.
Craige
@Jalayn Por favor, publique eso como respuesta. Es uno de los mejores enfoques.
Apoorv Khurasia
@MonsterTruck: No creo que deba hacerlo. La solución real sería migrar esta pregunta a SO y cerrarla como un duplicado de la pregunta a la que Jalayn se vincula.
Brian

Respuestas:

54

Si necesita un recuento de bucles dentro de un foreachbucle, ¿por qué no utiliza un forbucle normal ? El foreachbucle estaba destinado a simplificar los usos específicos de los forbucles. Parece que tiene una situación en la que la simplicidad de la foreachya no es beneficiosa.

Ryathal
fuente
1
+1 - buen punto. Es bastante fácil usar IEnumerable.Count u object.length con un ciclo for regular para evitar cualquier problema al calcular el número de elementos en el objeto.
11
@ GlenH7: Dependiendo de la implementación, IEnumerable.Countpodría ser costoso (o imposible, si es una enumeración única). Debe saber cuál es la fuente subyacente antes de poder hacerlo de forma segura.
Binario Worrier
2
-1: Bill Wagner en más eficaz C # describe por qué siempre hay que atenerse a foreachlo largo for (int i = 0…). Ha escrito un par de páginas, pero puedo resumirlo en dos palabras: optimización del compilador en la iteración. Ok, eso no fueron dos palabras.
Apoorv Khurasia
13
@MonsterTruck: sea lo que sea lo que escribió Bill Wagner, he visto suficientes situaciones como la descrita por el OP, donde un forbucle proporciona un mejor código legible (y la optimización teórica del compilador a menudo es una optimización prematura).
Doc Brown
1
@DocBrown Esto no es tan simple como la optimización prematura. Yo mismo identifiqué cuellos de botella en el pasado y los arreglé usando este enfoque (pero admito que estaba tratando con miles de objetos en la colección). Espero publicar un ejemplo de trabajo pronto. Mientras tanto, aquí está Jon Skeet . [Él dice que la mejora del rendimiento no será significativa, pero eso depende mucho de la colección y la cantidad de objetos].
Apoorv Khurasia
37

Puedes usar tipos anónimos como este

foreach (var item in items.Select((v, i) => new { Value = v, Index = i }))
{
    Console.WriteLine("Item value is {0} and index is {1}.", item.Value, item.Index);
}

Esto es similar a la enumeratefunción de Python

for i, v in enumerate(items):
    print v, i
Kris Harper
fuente
+1 Oh ... bombilla. Me gusta ese enfoque. ¿Por qué nunca lo he pensado antes?
Phil
17
Quien prefiera esto en C # sobre un bucle clásico tiene claramente una opinión extraña sobre la legibilidad. Esto puede ser un buen truco, pero no permitiría algo así en el código de calidad de producción. Es mucho más ruidoso que un bucle clásico y no se puede entender tan rápido.
Falcon
44
@Falcon, esta es una alternativa válida SI NO PUEDE USAR un bucle antiguo para el bucle (que necesita un recuento). Esas son algunas colecciones de objetos con las que tuve que trabajar ...
Fabricio Araujo
8
@FabricioAraujo: Seguramente C # para bucles no requiere un conteo. Hasta donde sé, son iguales a los C para los bucles, lo que significa que puede usar cualquier valor booleano para finalizar el bucle. Como una comprobación para el final de la enumeración cuando MoveNext devuelve falso.
Zan Lynx
1
Esto tiene el beneficio adicional de tener todo el código para manejar el incremento al comienzo del bloque foreach en lugar de tener que encontrarlo.
JeffO
13

Solo estoy agregando la respuesta de Dan Finch aquí, según lo solicitado.

No me des puntos, da puntos a Dan Finch :-)

La solución es escribir el siguiente método de extensión genérico:

public static void Each<T>( this IEnumerable<T> ie, Action<T, int> action )
{
    var i = 0;
    foreach ( var e in ie ) action( e, i++ );
}

Que se usa así:

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );
Jalayn
fuente
2
Eso es bastante inteligente, pero creo que usar un normal para es más "legible", especialmente si en el futuro alguien que termina manteniendo el código puede no ser un as en .NET y no está demasiado familiarizado con el concepto de métodos de extensión . Sin embargo, todavía estoy agarrando este pequeño código. :)
MetalMikester
1
Podría argumentar cualquier lado de esto, y la pregunta original: estoy de acuerdo con la intención de los carteles, hay casos en los que foreach lee mejor pero se necesita un índice, pero siempre soy adverso a agregar más azúcar sintáctico a un idioma si no es así muy necesario: tengo exactamente la misma solución en mi propio código con un nombre diferente: cadenas.ApplyIndexed <T> (......)
Mark Mullin
Estoy de acuerdo con Mark Mullin, puedes discutir cualquier lado. Pensé que era un uso bastante inteligente de los métodos de extensión y se ajustaba a la pregunta, así que lo compartí.
Jalayn
5

Puedo estar equivocado sobre esto, pero siempre pensé que el punto principal del bucle foreach era simplificar la iteración de los días STL de C ++, es decir

for(std::stltype<typename>::iterator iter = stl_type_instance.begin(); iter != stl_type_instance.end(); iter++)
{
   //dereference iter to use each object.
}

compare esto con el uso de .NET de IEnumerable en el foreach

foreach(TypeName instance in DescendentOfIEnumerable)
{
   //use instance to use the object.
}

este último es mucho más simple y evita muchas de las viejas trampas. Además, parecen seguir reglas casi idénticas. Por ejemplo, no puede cambiar el contenido IEnumerable mientras está dentro del bucle (lo que tiene mucho más sentido si lo piensa de la manera STL). Sin embargo, el bucle for a menudo tiene un propósito lógico diferente al de la iteración.

Jonathan Henson
fuente
44
+1: pocas personas recuerdan que foreach es básicamente azúcar sintáctico sobre el patrón iterador.
Steven Evers
1
Bueno, hay algunas diferencias; La manipulación del puntero donde está presente en .NET se oculta bastante profundamente del programador, pero podría escribir un bucle for que se pareciera mucho al ejemplo de C ++:for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
KeithS
@KeithS, no si te das cuenta de que todo lo que tocas en .NET que no es un tipo estructurado ES UN PUNTERO, solo con una sintaxis diferente (. En lugar de ->). De hecho, pasar un tipo de referencia a un método es lo mismo que pasarlo como un puntero y pasarlo por referencia es pasarlo como un doble puntero. La misma cosa. Al menos, esa es la forma en que una persona cuyo idioma nativo es C ++ lo piensa. Algo así, aprendes español en la escuela al pensar en cómo el idioma está sintácticamente relacionado con tu propio idioma nativo.
Jonathan Henson
* tipo de valor no tipo estructurado. lo siento.
Jonathan Henson
2

Si la colección en cuestión es una IList<T>, simplemente puede usar forcon indexación:

for (int i = 0; i < collection.Count; i++)
{
    var item = collection[i];
    // …
}

Si no, entonces podría usarlo Select(), tiene una sobrecarga que le da el índice.

Si eso tampoco es adecuado, creo que mantener ese índice manualmente es bastante simple, son solo dos líneas de código muy cortas. Crear una palabra clave específicamente para esto sería una exageración.

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}
svick
fuente
+1, también, si está usando un índice exactamente una vez dentro del ciclo, podría hacer algo como foo(++i)o foo(i++)según qué índice se necesita.
Trabajo
2

Si todo lo que sabe es que la colección es un IEnumerable, pero necesita realizar un seguimiento de cuántos elementos ha procesado hasta ahora (y, por lo tanto, el total cuando haya terminado), puede agregar un par de líneas a un bucle básico:

var coll = GetMyCollectionAsAnIEnumerable();
var idx = 0;
for(var e = coll.GetEnumerator(); e.MoveNext(); idx++)
{
   var elem = e.Current;

   //use elem and idx as you please
}

También puede agregar una variable de índice incremental a un foreach:

var i=0;
foreach(var elem in coll)
{
   //do your thing, then...
   i++;
}

Si desea que esto se vea más elegante, puede definir uno o dos métodos de extensión para "ocultar" estos detalles:

public static void ForEach<T>(this IEnumerable<T> input, Action<T> action)
{
    foreach(T elem in input)
        action(elem);
}

public static void ForEach<T>(this IEnumerable<T> input, Action<T, int> action)
{
    var idx = 0;
    foreach(T elem in input)
        action(elem, idx++); //post-increment happens after parameter-passing
}

//usage of the index-supporting method
coll.ForEach((e, i)=>Console.WriteLine("Element " + (i+1) + ": " + e.ToString()));
KeithS
fuente
1

El alcance también funciona si desea mantener el bloque limpio de colisiones con otros nombres ...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }
Arrendajo
fuente
-1

Para esto uso un whilebucle. Los bucles fortambién funcionan, y muchas personas parecen preferirlos, pero solo me gusta usarlos forcon algo que tenga un número fijo de iteraciones. IEnumerables puede generarse en tiempo de ejecución, por lo que, en mi opinión, whiletiene más sentido. Llámelo una directriz informal de codificación, si lo desea.

Robotman
fuente