c# sharp Como você obtém o índice da iteração atual de um loop foreach?




foreach.net core (24)

Se a coleção é uma lista, você pode usar List.IndexOf, como em:

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

Existe alguma construção de linguagem rara que não encontrei (como as poucas que aprendi recentemente, algumas no Stack Overflow) em C # para obter um valor representando a iteração atual de um loop foreach?

Por exemplo, atualmente faço algo assim dependendo das circunstâncias:

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

Resposta literal - aviso, o desempenho pode não ser tão bom quanto apenas usar um int para rastrear o índice. Pelo menos é melhor que usar IndexOf .

Você só precisa usar a sobrecarga de indexação de Select para agrupar cada item na coleção com um objeto anônimo que conhece o índice. Isso pode ser feito contra qualquer coisa 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);
}

Usando o LINQ, C # 7 e o pacote System.ValueTuple NuGet, você pode fazer isso:

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

Você pode usar a construção regular foreach e ser capaz de acessar o valor e o índice diretamente, não como um membro de um objeto, e manter ambos os campos apenas no escopo do loop. Por estas razões, acredito que esta é a melhor solução se você for capaz de usar C # 7 e System.ValueTuple .


Eu construí isso no 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.

Você também pode usar apenas string.join :

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

Finalmente, o C # 7 tem uma sintaxe decente para obter um índice dentro de um loop foreach (ou seja, tuplas):

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

Um pequeno método de extensão seria necessário:

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

Não concordo com comentários de que um loop for é a melhor escolha na maioria dos casos.

foreach é uma construção útil e não é substituída por um loop for em todas as circunstâncias.

Por exemplo, se você tiver um DataReader e percorrer todos os registros usando um foreach ele automaticamente chamará o método Dispose e fechará o leitor (que pode fechar a conexão automaticamente). Isto é, portanto, mais seguro, pois evita vazamentos de conexão, mesmo se você esquecer de fechar o leitor.

(Claro que é uma boa prática sempre fechar os leitores, mas o compilador não vai pegá-lo se você não o fizer - você não pode garantir que fechou todos os leitores, mas pode aumentar a probabilidade de não vazar conexões no hábito de usar foreach.)

Pode haver outros exemplos da chamada implícita do método Dispose sendo útil.


Eu não tinha certeza do que você estava tentando fazer com as informações do índice com base na pergunta. No entanto, em c #, normalmente você pode adaptar o método IEnumerable.Select para obter o índice do que você deseja. Por exemplo, eu poderia usar algo assim para saber se um valor é ímpar ou 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);

Isso lhe daria um dicionário por nome se o item era ímpar (1) ou mesmo (0) na lista.


Não há nada de errado em usar uma variável de contador. De fato, se você usa foreach while ou do , uma variável de contador deve ser declarada e incrementada em algum lugar.

Portanto, use esse idioma se você não tiver certeza se tem uma coleção indexada de forma adequada:

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

Senão use este se você sabe que sua coleção indexável é O (1) para acesso ao índice (que será para Array e provavelmente para List<T> (a documentação não diz), mas não necessariamente para outros 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 deve ser necessário "manualmente" operar o IEnumerator chamando MoveNext() e interrogando Current - foreach está salvando você que se incomoda ... se você precisar pular itens, apenas use um continue no corpo do loop.

E apenas para completar, dependendo do que você estava fazendo com seu índice (as construções acima oferecem bastante flexibilidade), você pode usar o 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));

Nós usamos AsParallel() acima, porque já é 2014, e queremos fazer bom uso desses múltiplos núcleos para acelerar as coisas. Além disso, para o LINQ 'sequencial', você só obtém um método de extensão ForEach() em List<T> e Array ... e não está claro que usá-lo é melhor do que fazer um foreach simples, já que você ainda está executando encadeado para sintaxe mais feia.


Por interesse, Phil Haack acabou de escrever um exemplo disso no contexto de um Delegado Razor Templated ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )

Efetivamente ele escreve um método de extensão que envolve a iteração em uma classe "IteratedItem" (veja abaixo) permitindo acesso ao índice assim como ao elemento durante a iteração.

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

No entanto, enquanto isso seria bom em um ambiente não-Razor se você está fazendo uma única operação (ou seja, um que poderia ser fornecido como um lambda) não vai ser uma substituição sólida da sintaxe for / foreach em contextos não-Razor .


Basta adicionar seu próprio índice. Mantenha simples.

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

C # 7 finalmente nos dá uma maneira elegante de fazer isso:

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

Ele só vai funcionar para uma lista e não qualquer IEnumerable, mas no LINQ há isto:

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 Eu não disse que foi uma ótima resposta, eu apenas disse que estava apenas mostrando que era possível fazer o que ele pediu :)

@Graphain Eu não esperaria que fosse rápido - eu não estou inteiramente certo de como isso funciona, ele poderia reiterar toda a lista toda vez que encontrar um objeto correspondente, o que seria um inferno de comparação.

Dito isso, a List pode manter um índice de cada objeto junto com a contagem.

Jonathan parece ter uma ideia melhor, se ele elaborasse?

Seria melhor apenas manter uma contagem de onde você está no foreach, porém, mais simples e mais adaptável.


Aqui está uma solução que acabei de criar 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 atualizado

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

Método de Extensão:

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

Que tal algo como isso? Observe que myDelimitedString pode ser nulo se myEnumerable estiver vazio.

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

Ian Mercer postou uma solução semelhante a esta no blog de Phil Haack :

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

Isso obtém o item ( item.value ) e seu índice ( item.i ) usando essa sobrecarga do Select do Linq :

o segundo parâmetro da função [inside Select] representa o índice do elemento de origem.

O new { i, value } está criando um novo objeto anônimo .

As alocações de heap podem ser evitadas usando ValueTuple se você estiver usando o C # 7.0 ou posterior:

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

Você também pode eliminar o item. usando desestruturação automática:

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

Usando a resposta do @ FlySwat, eu criei esta solução:

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

Você obtém o enumerador usando GetEnumerator e, em seguida, faz um loop usando um loop for . No entanto, o truque é tornar a condição do loop listEnumerator.MoveNext() == true .

Como o método MoveNext de um enumerador retorna true se houver um próximo elemento e ele puder ser acessado, fazendo com que a condição de loop faça o loop parar quando ficarmos sem elementos para iterar.


Acabei de ter esse problema, mas pensar no problema no meu caso deu a melhor solução, não relacionada à solução esperada.

Poderia ser um caso bastante comum, basicamente, eu estou lendo de uma lista de fontes e criando objetos baseados nelas em uma lista de destino, no entanto, eu tenho que verificar se os itens de origem são válidos primeiro e quero retornar a linha de qualquer erro. À primeira vista, quero obter o índice no enumerador do objeto na propriedade Current, no entanto, como estou copiando esses elementos, conheço implicitamente o índice atual de qualquer maneira a partir do destino atual. Obviamente, isso depende do seu objeto de destino, mas para mim era uma lista, e muito provavelmente ele implementaria o ICollection.

ou seja

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

Nem sempre aplicável, mas muitas vezes o suficiente para valer a pena mencionar, eu acho.

Enfim, o ponto é que às vezes há uma solução não óbvia já na lógica que você tem ...


A menos que sua coleção possa retornar o índice do objeto através de algum método, a única maneira é usar um contador como em seu exemplo.

No entanto, ao trabalhar com índices, a única resposta razoável para o problema é usar um loop for. Qualquer outra coisa introduz complexidade de código, sem mencionar complexidade de tempo e espaço.



Melhor usar a palavra-chave continue construção segura como esta

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

Você pode escrever o seu loop assim:

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

Depois de adicionar o seguinte método struct e extension.

O método struct e extension encapsula a funcionalidade 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);
    }
}

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

Isso funcionaria para coleções que suportam o IList .


É assim que eu faço, o que é bom por sua simplicidade / brevidade, mas se você está fazendo muito no corpo do laço obj.Value , ele vai ficar velho bem 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);
    ...
}

Eu não acredito que haja uma maneira de obter o valor da iteração atual de um loop foreach. Contando a si mesmo, parece ser o melhor caminho.

Posso perguntar por que você gostaria de saber?

Parece que você mais gostaria de estar fazendo uma das três coisas:

1) Obtendo o objeto da coleção, mas neste caso você já o possui.

2) Contando os objetos para posterior pós-processamento ... as coleções têm uma propriedade Count que você poderia utilizar.

3) Definir uma propriedade no objeto com base em sua ordem no loop ... embora você possa defini-la facilmente quando adicionou o objeto à coleção.





foreach