¿Cómo se obtiene el índice de la iteración actual de un bucle foreach?


939

¿Hay alguna construcción de lenguaje rara que no haya encontrado (como las pocas que he aprendido recientemente, algunas en Stack Overflow) en C # para obtener un valor que represente la iteración actual de un bucle foreach?

Por ejemplo, actualmente hago algo como esto dependiendo de las circunstancias:

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}

1
Por lo general, la recuperación de casting foreach no me va a ser más optimizada que simplemente usar el acceso basado en índices en una colección, aunque en muchos casos será igual. El propósito de foreach es hacer que su código sea legible, pero (generalmente) agrega una capa de indirección, que no es gratuita.
Brian

8
Yo diría que el objetivo principal de foreaches proporcionar un mecanismo de iteración común para todas las colecciones, independientemente de si son indexables ( List) o no ( Dictionary).
Brian Gideon

2
Hola, Brian Gideon: definitivamente estoy de acuerdo (esto fue hace unos años y tenía mucha menos experiencia en ese momento). Sin embargo, aunque Dictionaryno es indexable, una iteración de Dictionarylo atraviesa en un orden particular (es decir, un enumerador es indexable por el hecho de que produce elementos secuencialmente). En este sentido, podríamos decir que no estamos buscando el índice dentro de la colección, sino más bien el índice del elemento enumerado actual dentro de la enumeración (es decir, si estamos en el primer o quinto o último elemento enumerado).
Matt Mitchell

44
foreach también permite que el compilador omita los límites al verificar el acceso a cada matriz en el código compilado. Usar for con un índice hará que el tiempo de ejecución verifique si su acceso al índice es seguro.
IvoTops

1
Pero es falso. Si no cambia la variable de iteración de un bucle for dentro del bucle, el compilador sabe cuáles son sus límites y no necesita verificarlos nuevamente. Este es un caso tan común que cualquier compilador decente lo implementará.
Jim Balter

Respuestas:


552

El foreaches para iterar sobre colecciones que implementan IEnumerable. Lo hace llamando GetEnumeratora la colección, que devolverá unEnumerator .

Este enumerador tiene un método y una propiedad:

  • MoveNext ()
  • Actual

Currentdevuelve el objeto en el que se encuentra Enumerator actualmente, MoveNextactualizaCurrent al siguiente objeto.

El concepto de índice es extraño al concepto de enumeración y no se puede hacer.

Debido a eso, la mayoría de las colecciones pueden atravesarse utilizando un indexador y la construcción del bucle for.

Prefiero utilizar un bucle for en esta situación en comparación con el seguimiento del índice con una variable local.


165
"Obviamente, el concepto de índice es ajeno al concepto de enumeración y no se puede hacer". - Esto no tiene sentido, como dejan en claro las respuestas de David B y bcahill. Un índice es una enumeración en un rango, y no hay razón para que uno no pueda enumerar dos cosas en paralelo ... eso es exactamente lo que hace la forma de indexación de Enumerable.Select.
Jim Balter

11
Ejemplo de código básico:for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
Chad Hedgcock el

22
@ JimBalter: Ese es el primer enlace que leo. No veo cómo apoya tu posición. Tampoco lanzo ad-homs y palabras divisivas a las personas cuando respondo. Cito como ejemplo su uso de "tonterías", "confusión extraordinaria", "completamente equivocado y confundido", "Obtuve 37 votos a favor", etc. (Creo que su ego está un poco en juego aquí). Quisiera pedirle cortésmente que no intimide a los demás con su 'argumento ad verecundiam' y, en cambio, me gustaría animarlo a pensar en formas de apoyar a las personas aquí en StackOverflow. Yo diría que Jon Skeet es un ciudadano modelo aquí en ese sentido.
Pretzel

11
Pretzel: Su uso de Jon Skeet como ciudadano modelo está equivocado, ya que él cegará (y rechazará) a alguien que no está de acuerdo con él, como he experimentado. Jim Balter: Algunos de sus comentarios fueron demasiado agresivos y podría haber presentado sus puntos con menos enojo y más educación.
Suncat2000

77
El punto de @Pretzel Jim es que siempre que pueda asignar los elementos a una secuencia de enteros, puede indexarlo. Que la clase en sí misma no almacene un índice no viene al caso. Además, que la lista enlazada hace tener un pedido sólo refuerza la posición de Jim. Todo lo que necesita hacer es numerar cada elemento en orden. Específicamente, puede lograr esto incrementando un conteo mientras itera, o podría generar una lista de enteros con la misma longitud y luego comprimirlos (como en la zipfunción de Python ).
jpmc26

666

Ian Mercer publicó una solución similar a esta en el blog de Phil Haack :

foreach (var item in Model.Select((value, i) => new { i, value }))
{
    var value = item.value;
    var index = item.i;
}

Esto le proporciona el elemento ( item.value) y su índice ( item.i) mediante el uso de esta sobrecarga de LINQSelect :

El segundo parámetro de la función [dentro de Seleccionar] representa el índice del elemento fuente.

El new { i, value }está creando un nuevo objeto anónimo .

Las asignaciones de montón se pueden evitar ValueTuplesi se usa C # 7.0 o posterior:

foreach (var item in Model.Select((value, i) => ( value, i )))
{
    var value = item.value;
    var index = item.i;
}

También puede eliminarlo item.utilizando la desestructuración automática:

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>

99
Esa solución es buena para el caso de una plantilla Razor donde la pulcritud de la plantilla es una preocupación de diseño no trivial y también desea utilizar el índice de cada elemento enumerado. Sin embargo, tenga en cuenta que la asignación de objetos del 'ajuste' agrega gastos (en espacio y tiempo) además del incremento (inevitable) de un número entero.
David Bullock

66
@mjsr La documentación está aquí .
Thorkil Holm-Jacobsen

19
Lo siento, eso es inteligente, pero ¿es realmente más legible que crear un índice fuera del foreach e incrementarlo en cada ciclo?
jbyrd

13
Con las versiones posteriores de C # para que también use tuplas, tendrá algo como esto: foreach (var (item, i) en Model.Select ((v, i) => (v, i))) Le permite acceda al ítem e índice (i) directamente dentro del ciclo for con deconstrucción de tuplas.
Haukman

55
¿Alguien puede explicarme por qué esta es una buena respuesta (más de 450 votos al momento de escribir esto)? Hasta donde puedo ver, es más difícil de entender que simplemente incrementar un contador, por lo tanto, es menos fácil de mantener, usa más memoria y probablemente sea más lento. ¿Me estoy perdiendo de algo?
Rich N

182

Finalmente, C # 7 tiene una sintaxis decente para obtener un índice dentro de un foreachbucle (es decir, tuplas):

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}

Se necesitaría un pequeño método de extensión:

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index)); 

8
Esta respuesta está subestimada, tener las tuplas es mucho más limpio
Todd

77
Modificado para manejar colecciones nulas:public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
2Toad

Buena esa. Realmente me gusta esta solución.
FranzHuber23

Esta es la mejor respuesta
w0ns88

2
Puede ser útil llamar al método Enumeratedpara que sea más reconocible para las personas acostumbradas a otros idiomas (y quizás también cambie el orden de los parámetros de tupla). No WithIndexes que no sea obvio de todos modos.
FernAndr

115

Podría hacer algo como esto:

public static class ForEachExtensions
{
    public static void ForEachWithIndex<T>(this IEnumerable<T> enumerable, Action<T, int> handler)
    {
        int idx = 0;
        foreach (T item in enumerable)
            handler(item, idx++);
    }
}

public class Example
{
    public static void Main()
    {
        string[] values = new[] { "foo", "bar", "baz" };

        values.ForEachWithIndex((item, idx) => Console.WriteLine("{0}: {1}", idx, item));
    }
}

12
Eso no "realmente" resuelve el problema. La idea es buena pero no evita la variable de conteo adicional
Atmocreations

Esto no funciona si tenemos una declaración de devolución dentro de nuestro ciclo for, si cambia "ForEachWithIndex" por eso, entonces no es genérico, mejor escribir un ciclo for regular
Shankar Raju

Su llamada ForEachWithIndex es equivalente a esta usando el Linq Select que toma una cadena y un índice:values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
user2023861

95

No estoy de acuerdo con los comentarios de que un for bucle es una mejor opción en la mayoría de los casos.

foreach es una construcción útil, y no reemplazable por un for bucle en todas las circunstancias.

Por ejemplo, si tiene un DataReader y recorre todos los registros usando un foreach, automáticamente llama a Dispose método y cierra el lector (que luego puede cerrar la conexión automáticamente). Por lo tanto, esto es más seguro ya que evita fugas de conexión incluso si olvida cerrar el lector.

(Seguro, es una buena práctica cerrar siempre los lectores, pero el compilador no lo detectará si no lo hace; no puede garantizar que haya cerrado todos los lectores, pero puede hacer que sea más probable que no pierda conexiones al obtener en el hábito de usar foreach.)

Puede haber otros ejemplos de que la llamada implícita del Disposemétodo sea útil.


2
Gracias por señalar esto. Más bien sutil. Puede obtener más información en pvle.be/2010/05/foreach-statement-calls-dispose-on-ienumerator y msdn.microsoft.com/en-us/library/aa664754(VS.71).aspx .
Mark Meuer

+1. Estaba escribiendo más en detalle sobre cómo foreaches diferente for(y más cercano while) en Programmers.SE .
Arseni Mourzenko

64

Respuesta literal: advertencia, el rendimiento puede no ser tan bueno como simplemente usar un intpara rastrear el índice. Al menos es mejor que usarIndexOf .

Solo necesita usar la sobrecarga de indexación de Select para ajustar cada elemento de la colección con un objeto anónimo que conozca el índice. Esto se puede hacer contra cualquier cosa que implemente IEnumerable.

System.Collections.IEnumerable collection = Enumerable.Range(100, 10);

foreach (var o in collection.OfType<object>().Select((x, i) => new {x, i}))
{
    Console.WriteLine("{0} {1}", o.i, o.x);
}

3
La única razón para usar OfType <T> () en lugar de Cast <T> () es si algunos elementos de la enumeración pueden fallar en una conversión explícita. Por objeto, este nunca será el caso.
dahlbyk

13
Claro, excepto por la otra razón para usar OfType en lugar de Cast, que es que nunca uso Cast.
Amy B

36

Con LINQ, C # 7 y el System.ValueTuplepaquete NuGet, puede hacer esto:

foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
    Console.WriteLine(value + " is at index " + index);
}

Puede usar la foreachconstrucción regular y poder acceder al valor y al índice directamente, no como miembro de un objeto, y mantiene ambos campos solo en el alcance del bucle. Por estas razones, creo que esta es la mejor solución si puede usar C # 7 y System.ValueTuple.


¿Cómo es esto diferente de la respuesta del usuario1414213562 ?
Edward Brey

@ EdwardBrey Supongo que no. Esta pregunta tiene muchas respuestas, probablemente me la perdí o no me di cuenta de lo que estaba haciendo porque separó parte de la lógica en un método de extensión.
Pavel

1
Esto es diferente porque .Select está integrado desde LINQ. ¿No tienes que escribir tu propia función? Sin embargo, necesitará que VS instale "System.ValueTuple".
Anton

33

Usando la respuesta de @ FlySwat, se me ocurrió esta solución:

//var list = new List<int> { 1, 2, 3, 4, 5, 6 }; // Your sample collection

var listEnumerator = list.GetEnumerator(); // Get enumerator

for (var i = 0; listEnumerator.MoveNext() == true; i++)
{
  int currentItem = listEnumerator.Current; // Get current item.
  //Console.WriteLine("At index {0}, item is {1}", i, currentItem); // Do as you wish with i and  currentItem
}

Obtiene el enumerador usando GetEnumeratory luego realiza un bucle con un forbucle. Sin embargo, el truco es hacer que la condición del bucle listEnumerator.MoveNext() == true.

Dado que el MoveNextmétodo de un enumerador devuelve verdadero si hay un siguiente elemento y se puede acceder a él, lo que hace que la condición del bucle detenga el bucle cuando nos quedemos sin elementos para iterar.


10
No es necesario comparar listEnumerator.MoveNext () == true. Eso es como preguntarle a la computadora si verdadero == verdadero? :) Solo diga si listEnumerator.MoveNext () {}
Zesty

99
@Zesty, tienes toda la razón. Sentí que es más legible agregarlo en este caso, especialmente para las personas que no están acostumbradas a ingresar nada más que i <blahSize como condición.
Gezim

@Gezim No me importa predicados aquí, pero entiendo que esto no parece un predicado.
John Dvorak

1
Debe deshacerse del enumerador.
Antonín Lejsek

@ AntonínLejsek El enumerador no implementa IDisposable.
Edward Brey

27

No hay nada de malo en usar una variable de contador. De hecho, si se utiliza for, foreach whileodo , una variable de contador debe declararse e incrementarse en algún lugar.

Así que usa este modismo si no estás seguro si tienes una colección indexada adecuadamente:

var i = 0;
foreach (var e in collection) {
   // Do stuff with 'e' and 'i'
   i++;
}

De lo contrario, use este si sabe que su colección indexable es O (1) para el acceso al índice (que será para Arrayy probablemente para List<T>(la documentación no dice), pero no necesariamente para otros tipos (como LinkedList)):

// Hope the JIT compiler optimises read of the 'Count' property!
for (var i = 0; i < collection.Count; i++) {
   var e = collection[i];
   // Do stuff with 'e' and 'i'
}

Nunca debería ser necesario operarlo 'manualmente' IEnumeratorinvocando MoveNext()e interrogando Current: le foreachestá ahorrando esa molestia particular ... si necesita omitir elementos, simplemente use uncontinue en el cuerpo del bucle.

Y solo para completar, dependiendo de lo que estaba haciendo con su índice (las construcciones anteriores ofrecen mucha flexibilidad), puede usar Parallel LINQ:

// First, filter 'e' based on 'i',
// then apply an action to remaining 'e'
collection
    .AsParallel()
    .Where((e,i) => /* filter with e,i */)
    .ForAll(e => { /* use e, but don't modify it */ });

// Using 'e' and 'i', produce a new collection,
// where each element incorporates 'i'
collection
    .AsParallel()
    .Select((e, i) => new MyWrapper(e, i));

Usamos lo AsParallel()anterior, porque ya es 2014, y queremos hacer un buen uso de esos múltiples núcleos para acelerar las cosas. Además, para LINQ 'secuencial', solo obtienes un ForEach()método de extensión List<T>yArray ... y no está claro que usarlo sea mejor que hacer un simple foreach, ya que todavía estás ejecutando un solo subproceso para una sintaxis más fea.


26

Puede ajustar el enumerador original con otro que contenga la información del índice.

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

Aquí está el código para la ForEachHelperclase.

public static class ForEachHelper
{
    public sealed class Item<T>
    {
        public int Index { get; set; }
        public T Value { get; set; }
        public bool IsLast { get; set; }
    }

    public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
    {
        Item<T> item = null;
        foreach (T value in enumerable)
        {
            Item<T> next = new Item<T>();
            next.Index = 0;
            next.Value = value;
            next.IsLast = false;
            if (item != null)
            {
                next.Index = item.Index + 1;
                yield return item;
            }
            item = next;
        }
        if (item != null)
        {
            item.IsLast = true;
            yield return item;
        }            
    }
}

En realidad, esto no devolverá el índice del artículo. En su lugar, devolverá el índice dentro de la lista enumerada, que puede ser solo una sublista de la lista, lo que le dará datos precisos solo cuando la sublista y la lista tengan el mismo tamaño. Básicamente, cada vez que la colección tenga objetos dentro del tipo solicitado, su índice será incorrecto.
Lucas B

77
@Lucas: No, pero devolverá el índice de la iteración foreach actual. Esa fue la pregunta.
Brian Gideon

20

Aquí hay una solución que se me ocurrió para este problema

Código original:

int index=0;
foreach (var item in enumerable)
{
    blah(item, index); // some code that depends on the index
    index++;
}

Código actualizado

enumerable.ForEach((item, index) => blah(item, index));

Método de extensión:

    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> action)
    {
        var unit = new Unit(); // unit is a new type from the reactive framework (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx) to represent a void, since in C# you can't return a void
        enumerable.Select((item, i) => 
            {
                action(item, i);
                return unit;
            }).ToList();

        return pSource;
    }

16

Simplemente agregue su propio índice. Mantenlo simple.

int i = 0;
foreach (var item in Collection)
{
    item.index = i;
    ++i;
}

15

Solo funcionará para una Lista y no para IEnumerable, pero en LINQ hay esto:

IList<Object> collection = new List<Object> { 
    new Object(), 
    new Object(), 
    new Object(), 
    };

foreach (Object o in collection)
{
    Console.WriteLine(collection.IndexOf(o));
}

Console.ReadLine();

@ Jonathan No dije que era una gran respuesta, solo dije que solo estaba demostrando que era posible hacer lo que me pidió :)

@Graphain No esperaría que fuera rápido: no estoy completamente seguro de cómo funciona, podría reiterar a través de toda la lista cada vez que encuentre un objeto que coincida, lo que sería una gran cantidad de comparaciones.

Dicho esto, List podría mantener un índice de cada objeto junto con el recuento.

¿Jonathan parece tener una mejor idea, si quisiera dar más detalles?

Sin embargo, sería mejor llevar un recuento de lo que estás haciendo en el foreach, más simple y más adaptable.


55
No estoy seguro sobre el fuerte voto negativo. ¡El rendimiento seguro hace que esto sea prohibitivo, pero respondiste la pregunta!
Matt Mitchell el

44
Otro problema con esto es que solo funciona si los elementos de la lista son únicos.
CodesInChaos

14

C # 7 finalmente nos da una forma elegante de hacer esto:

static class Extensions
{
    public static IEnumerable<(int, T)> Enumerate<T>(
        this IEnumerable<T> input,
        int start = 0
    )
    {
        int i = start;
        foreach (var t in input)
        {
            yield return (i++, t);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var s = new string[]
        {
            "Alpha",
            "Bravo",
            "Charlie",
            "Delta"
        };

        foreach (var (i, t) in s.Enumerate())
        {
            Console.WriteLine($"{i}: {t}");
        }
    }
}

Sí, y MS debería extender el CLR / BCL para hacer que este tipo de cosas sea nativo.
Todd

14

¿Por qué foreach?

La forma más simple es usar for en lugar de foreach si está usando List :

for (int i = 0 ; i < myList.Count ; i++)
{
    // Do something...
}

O si quieres usar foreach:

foreach (string m in myList)
{
     // Do something...
}

Puede usar esto para conocer el índice de cada ciclo:

myList.indexOf(m)

8
La solución indexOf no es válida para la lista con duplicados y también es muy lenta.
tymtam

2
El problema que debe evitarse es cuando atraviesa IEnumerable varias veces, por ejemplo, para obtener el recuento de elementos y luego cada elemento. Esto tiene implicaciones cuando IEnumerable es el resultado de una consulta de base de datos, por ejemplo.
David Clarke el

2
myList.IndexOf () es O (n), por lo que su bucle será O (n ^ 2).
Patrick Beard

9
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

Esto funcionaría para colecciones de apoyo IList.


68
Dos problemas: 1) Esto se debe a O(n^2)que en la mayoría de las implementaciones IndexOfes O(n). 2) Esto falla si hay elementos duplicados en la lista.
CodesInChaos

15
Nota: O (n ^ 2) significa que esto podría ser desastrosamente lento para una gran colección.
O'Rooney

¡Gran invento para usar el método IndexOf! ¡Esto es lo que estaba buscando para obtener el índice (número) en cada ciclo! Big Thx
Mitja Bonca

19
Dios, ¡espero que no hayas usado eso! :( SÍ usa esa variable que no quería crear; de hecho, creará n + 1 ints porque esa función tiene que crear una para poder regresar también, y ese índice de búsqueda es mucho, mucho más lento que operación de incremento de un número entero en cada paso. ¿Por qué la gente no votará esta respuesta? ''
canahari

13
No use esta respuesta, encontré la dura verdad mencionada en uno de los comentarios. "Esto falla si hay elementos duplicados en la lista".
Bruce

9

Así es como lo hago, lo cual es bueno por su simplicidad / brevedad, pero si estás haciendo mucho en el cuerpo del bucle obj.Value, envejecerá bastante rápido.

foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
    string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
    ...
}

8

Esta respuesta: presionar al equipo de lenguaje C # para obtener soporte directo de idioma.

La respuesta principal dice:

Obviamente, el concepto de índice es ajeno al concepto de enumeración, y no se puede hacer.

Si bien esto es cierto para la versión actual del lenguaje C # (2020), este no es un límite conceptual de CLR / Lenguaje, se puede hacer.

El equipo de desarrollo del lenguaje C # de Microsoft podría crear una nueva característica del lenguaje C #, al agregar soporte para una nueva interfaz IIndexedEnumerable

foreach (var item in collection with var index)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

//or, building on @user1414213562's answer
foreach (var (item, index) in collection)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

Si foreach ()se usa y with var indexestá presente, el compilador espera que la colección de elementos declare la IIndexedEnumerableinterfaz. Si la interfaz está ausente, el compilador puede rellenar la fuente con un objeto IndexedEnumerable, que agrega el código para rastrear el índice.

interface IIndexedEnumerable<T> : IEnumerable<T>
{
    //Not index, because sometimes source IEnumerables are transient
    public long IterationNumber { get; }
}

Más tarde, el CLR puede actualizarse para tener un seguimiento de índice interno, que solo se usa si withse especifica la palabra clave y la fuente no se implementa directamenteIIndexedEnumerable

Por qué:

  • Foreach se ve mejor y, en aplicaciones comerciales, los bucles foreach rara vez son un cuello de botella en el rendimiento
  • Foreach puede ser más eficiente en la memoria. Tener una canalización de funciones en lugar de convertir a nuevas colecciones en cada paso. ¿A quién le importa si usa algunos ciclos de CPU más cuando hay menos fallas de caché de CPU y menos recolecciones de basura?
  • Requerir que el codificador agregue un código de seguimiento de índice, arruina la belleza
  • Es bastante fácil de implementar (por favor, Microsoft) y es compatible con versiones anteriores.

Si bien la mayoría de las personas aquí no son empleados de Microsoft, esta es una respuesta correcta, puede presionar a Microsoft para que agregue dicha función. Ya podría construir su propio iterador con una función de extensión y usar tuplas , pero Microsoft podría rociar el azúcar sintáctico para evitar la función de extensión


Espera, ¿esta característica del lenguaje ya existe o se propone para el futuro?
Pavel

1
@Pavel He actualizado la respuesta para que quede clara. Esta respuesta se proporcionó para contrarrestar la respuesta principal que dice "Obviamente, el concepto de índice es extraño al concepto de enumeración y no se puede hacer".
Todd

6

Si la colección es una lista, puede usar List.IndexOf, como en:

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}

13
Y ahora el algoritmo es O (n ^ 2) (si no peor). Pensaría con mucho cuidado antes de usar esto. También es un duplicado de la respuesta de
@crucible

1
@BradleyDotNET es exactamente correcto, no use esta versión.
Oskar

1
¡Ten cuidado con esto! Si tiene un elemento duplicado en su lista, ¡obtendrá la posición del primero!
Sonhja

5

Es mejor usar continueuna construcción segura de palabras clave como esta

int i=-1;
foreach (Object o in collection)
{
    ++i;
    //...
    continue; //<--- safe to call, index will be increased
    //...
}

5

Puedes escribir tu bucle así:

var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
    System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}

Después de agregar la siguiente estructura y método de extensión.

La estructura y el método de extensión encapsulan la funcionalidad Enumerable.Select.

public struct ValueWithIndex<T>
{
    public readonly T Value;
    public readonly int Index;

    public ValueWithIndex(T value, int index)
    {
        this.Value = value;
        this.Index = index;
    }

    public static ValueWithIndex<T> Create(T value, int index)
    {
        return new ValueWithIndex<T>(value, index);
    }
}

public static class ExtensionMethods
{
    public static IEnumerable<ValueWithIndex<T>> GetEnumeratorWithIndex<T>(this IEnumerable<T> enumerable)
    {
        return enumerable.Select(ValueWithIndex<T>.Create);
    }
}

3

Mi solución para este problema es un método de extensión WithIndex(),

http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs

Úselo como

var list = new List<int> { 1, 2, 3, 4, 5, 6 };    

var odd = list.WithIndex().Where(i => (i.Item & 1) == 1);
CollectionAssert.AreEqual(new[] { 0, 2, 4 }, odd.Select(i => i.Index));
CollectionAssert.AreEqual(new[] { 1, 3, 5 }, odd.Select(i => i.Item));

Usaría un structpara el par (índice, elemento).
CodesInChaos

3

Por interés, Phil Haack acaba de escribir un ejemplo de esto en el contexto de un delegado de Razor Templated ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )

Efectivamente, escribe un método de extensión que envuelve la iteración en una clase "IteratedItem" (ver más abajo) permitiendo el acceso al índice y al elemento durante la iteración.

public class IndexedItem<TModel> {
  public IndexedItem(int index, TModel item) {
    Index = index;
    Item = item;
  }

  public int Index { get; private set; }
  public TModel Item { get; private set; }
}

Sin embargo, si bien esto estaría bien en un entorno que no sea Razor si está haciendo una sola operación (es decir, una que podría proporcionarse como una lambda), no será un reemplazo sólido de la sintaxis de for / foreach en contextos que no sean Razor .


3

No creo que esto deba ser bastante eficiente, pero funciona:

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}

3

Construí esto en LINQPad :

var listOfNames = new List<string>(){"John","Steve","Anna","Chris"};

var listCount = listOfNames.Count;

var NamesWithCommas = string.Empty;

foreach (var element in listOfNames)
{
    NamesWithCommas += element;
    if(listOfNames.IndexOf(element) != listCount -1)
    {
        NamesWithCommas += ", ";
    }
}

NamesWithCommas.Dump();  //LINQPad method to write to console.

También puedes usar string.join:

var joinResult = string.Join(",", listOfNames);

También puede usar string.join en c # Por ejemplo: var joinResult = string.Join (",", listOfNames); '
Warren LaFrance

1
¿Alguien puede explicarme qué es esto O (n * n)?
Axel

@Axel básicamente significa que las operaciones requeridas para calcular el resultado aumentan de forma cuadrática, es decir, si hay nelementos, entonces las operaciones son n * no ncuadráticas. en.wikipedia.org/wiki/…
Richard Hansell

2

No creo que haya una manera de obtener el valor de la iteración actual de un bucle foreach. Contarte a ti mismo, parece ser la mejor manera.

¿Puedo preguntar por qué quieres saber?

Parece que lo más probable es que estés haciendo una de tres cosas:

1) Obtener el objeto de la colección, pero en este caso ya lo tiene.

2) Contando los objetos para su posterior procesamiento ... las colecciones tienen una propiedad Count que puede utilizar.

3) Establecer una propiedad en el objeto en función de su orden en el bucle ... aunque podría configurarlo fácilmente al agregar el objeto a la colección.


4) El caso que he tocado varias veces es algo diferente que debe hacerse en la primera o la última pasada: digamos una lista de objetos que va a imprimir y necesita comas entre los elementos, pero no después del último elemento.
Loren Pechtel

2

A menos que su colección pueda devolver el índice del objeto mediante algún método, la única forma es usar un contador como en su ejemplo.

Sin embargo, cuando se trabaja con índices, la única respuesta razonable al problema es usar un bucle for. Cualquier otra cosa introduce la complejidad del código, sin mencionar la complejidad del tiempo y el espacio.


2

¿Qué tal algo como esto? Tenga en cuenta que myDelimitedString puede ser nulo si myEnumerable está vacío.

IEnumerator enumerator = myEnumerable.GetEnumerator();
string myDelimitedString;
string current = null;

if( enumerator.MoveNext() )
    current = (string)enumerator.Current;

while( null != current)
{
    current = (string)enumerator.Current; }

    myDelimitedString += current;

    if( enumerator.MoveNext() )
        myDelimitedString += DELIMITER;
    else
        break;
}

Múltiples problemas aquí. A) abrazadera extra. B) string concat + = por iteración de bucle.
enorl76

2

Acabo de tener este problema, pero pensar en el problema en mi caso dio la mejor solución, sin relación con la solución esperada.

Podría ser un caso bastante común, básicamente, estoy leyendo de una lista de origen y creando objetos basados ​​en ellos en una lista de destino, sin embargo, tengo que verificar si los elementos de origen son válidos primero y quiero devolver la fila de cualquier error. A primera vista, quiero obtener el índice en el enumerador del objeto en la propiedad Current, sin embargo, como estoy copiando estos elementos, implícitamente conozco el índice actual de todos modos desde el destino actual. Obviamente, depende de su objeto de destino, pero para mí fue una Lista, y lo más probable es que implemente ICollection.

es decir

var destinationList = new List<someObject>();
foreach (var item in itemList)
{
  var stringArray = item.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);

  if (stringArray.Length != 2)
  {
    //use the destinationList Count property to give us the index into the stringArray list
    throw new Exception("Item at row " + (destinationList.Count + 1) + " has a problem.");
  }
  else
  {
    destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]});
  }
}

No siempre es aplicable, pero a menudo creo que vale la pena mencionarlo.

De todos modos, el punto es que a veces ya hay una solución no obvia en la lógica que tienes ...


2

No estaba seguro de lo que estaba tratando de hacer con la información del índice basada en la pregunta. Sin embargo, en C #, generalmente puede adaptar el método IEnumerable.Select para obtener el índice de lo que desee. Por ejemplo, podría usar algo como esto para saber si un valor es impar o par.

string[] names = { "one", "two", "three" };
var oddOrEvenByName = names
    .Select((name, index) => new KeyValuePair<string, int>(name, index % 2))
    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

Esto le daría un diccionario por nombre de si el elemento era impar (1) o par (0) en la lista.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.