valores - recorrer una lista c#




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

¿Hay algún constructo de lenguaje raro que no haya encontrado (como los pocos que he aprendido recientemente, algunos sobre Desbordamiento de pila) en C # para obtener un valor que representa 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++;
}

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

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

A menos que su colección pueda devolver el índice del objeto a través de 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.


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;
    }

Así es como lo hago, lo cual es bueno por su simplicidad / brevedad, pero si estás haciendo mucho en el valor del cuerpo del bucle obj.Value , va a 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);
    ...
}

Construí esto en LINQPad :

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));

También string.join usar string.join :

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;
}

Finalmente, C # 7 tiene una sintaxis decente para obtener un índice dentro de un bucle foreach (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)); 

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 de C #, esto no es un límite conceptual.

La creación de una nueva función de lenguaje C # por parte de MS podría resolver esto, junto con el soporte para una nueva interfaz IIndexedEnumerable

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

Si foreach se pasa un IEnumerable y no puede resolver un IIndexedEnumerable, pero se le pregunta con el índice var, entonces el compilador de C # puede envolver la fuente con un objeto IndexedEnumerable, que agrega el código para el seguimiento del índice.

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);
    }
}

Por qué:

  • Foreach se ve mejor, y en aplicaciones de negocios rara vez es un cuello de botella de rendimiento
  • Foreach puede ser más eficiente en la memoria. Tener un conjunto de funciones en lugar de convertir a nuevas colecciones en cada paso. A quién le importa si utiliza unos cuantos ciclos de CPU más, si hay menos fallos de caché de CPU y menos GC.Collects
  • Requerir que el codificador agregue un código de seguimiento de índice, estropea la belleza
  • Es bastante fácil de implementar (gracias a MS) y es compatible con versiones anteriores

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


Mejor usar palabras clave continue la construcción segura como esta

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

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

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

No creo que haya una manera de obtener el valor de la iteración actual de un bucle foreach. Contándote, parece ser la mejor manera.

¿Puedo preguntar, por qué quieres saber?

Parece que probablemente estarías haciendo una de tres cosas:

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

2) Contando los objetos para su posterior procesamiento posterior ... 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.


No hay nada de malo en usar una variable de contador. De hecho, ya sea que use, foreach while o do , una variable de contador debe ser declarada e incrementada en alguna parte.

Entonces, use este idioma si no está seguro de tener una colección adecuadamente indexada:

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 Array y probablemente para la List<T> (la documentación no lo dice), pero no necesariamente para otros tipos (como 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 operar manualmente el IEnumerator invocando MoveNext() e interrogando a Current - foreach está ahorrando esa molestia en particular ... si necesita omitir elementos, solo use continue en el cuerpo del bucle.

Y solo para estar completo, dependiendo de lo que estabas haciendo con tu índice (las construcciones anteriores ofrecen mucha flexibilidad), puedes 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 AsParallel() arriba, 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 método de extensión ForEach() en List<T> y Array ... y no está claro que usarlo sea mejor que hacer un simple foreach , ya que todavía estás ejecutando single- roscado para la sintaxis más fea.


Para interesar, Phil Haack acaba de escribir un ejemplo de esto en el contexto de un delegado con plantilla de Razor ( 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 de "IteratedItem" (ver más abajo) que permite el acceso al índice y al elemento durante la iteración.

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

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


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));
    }
}

Puedes escribir tu bucle así:

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; }
}

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.

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);

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

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.

Solo agrega tu propio índice. Mantenlo simple.

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);
}

Usando LINQ, C # 7 y el paquete System.ValueTuple 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 construcción regular de foreach y poder acceder al valor y al índice directamente, no como miembro de un objeto, y mantiene ambos campos solo en el ámbito del bucle. Por estas razones, creo que esta es la mejor solución si puede usar C # 7 y System.ValueTuple .


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 utilizando GetEnumerator y luego realiza un bucle utilizando un bucle for . Sin embargo, el truco es hacer que la condición listEnumerator.MoveNext() == true del bucle sea listEnumerator.MoveNext() == true .

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


¿Por qué foreach?

La forma más sencilla 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...       
}

Puedes usar esto para calcular el índice de cada Loop:

myList.indexOf(m)

No estaba seguro de lo que intentaba 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 determinar si un valor es par o impar.

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]});
  }
}

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





foreach