Existe alguma construção de linguagem rara que não encontrei (como as poucas que aprendi recentemente, algumas sobre Stack Overflow) em C # para obter um valor que representa 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++;
}
foreach
é fornecer um mecanismo de iteração comum para todas as coleções, independentemente de serem indexáveis (List
) ou não (Dictionary
).Dictionary
não seja indexável, uma iteração deDictionary
o percorre em uma ordem específica (ou seja, um Enumerador é indexável pelo fato de produzir elementos sequencialmente). Nesse sentido, poderíamos dizer que não estamos procurando o índice dentro da coleção, mas o índice do elemento enumerado atual na enumeração (isto é, se estamos no primeiro, quinto ou último elemento enumerado).Respostas:
O
foreach
é para iterar sobre coleções implementadasIEnumerable
. Isso é feito chamandoGetEnumerator
a coleção, que retornará umEnumerator
.Este enumerador possui um método e uma propriedade:
Current
retorna o objeto em que o Enumerator está atualmente,MoveNext
atualizaCurrent
para o próximo objeto.O conceito de um índice é estranho ao conceito de enumeração e não pode ser feito.
Por esse motivo, a maioria das coleções pode ser percorrida usando um indexador e a construção do loop for.
Eu prefiro usar um loop for nessa situação em comparação com o rastreamento do índice com uma variável local.
fonte
for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
zip
função do Python ).Ian Mercer postou uma solução semelhante a esta no blog de Phil Haack :
Isso fornece o item (
item.value
) e seu índice (item.i
) usando essa sobrecarga de LINQsSelect
: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:Você também pode eliminar
item.
usando destruturação automática:fonte
Finalmente, o C # 7 tem uma sintaxe decente para obter um índice dentro de um
foreach
loop (ou seja, tuplas):Um pequeno método de extensão seria necessário:
fonte
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
Enumerated
para ser mais reconhecível para pessoas acostumadas a outros idiomas (e talvez trocar a ordem dos parâmetros da tupla também). Não que issoWithIndex
não seja óbvio de qualquer maneira.Poderia fazer algo assim:
fonte
values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
Discordo dos comentários de que um
for
loop é uma escolha melhor na maioria dos casos.foreach
é uma construção útil e não substituível por umfor
loop em todas as circunstâncias.Por exemplo, se você tiver um DataReader e percorrer todos os registros usando um,
foreach
ele automaticamente chama o método Dispose e fecha o leitor (que pode fechar a conexão automaticamente). Portanto, é mais seguro, pois evita vazamentos de conexão, mesmo que você esqueça de fechar o leitor.(Claro que é uma boa prática sempre fechar os leitores, mas o compilador não será capaz de capturá-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 perder conexões obtendo o hábito de usar o foreach.)
Pode haver outros exemplos de chamadas implícitas do
Dispose
método sendo úteis.fonte
foreach
é diferentefor
(e mais próximowhile
) do Programmers.SE .Resposta literal - aviso, o desempenho pode não ser tão bom quanto usar um
int
para rastrear o índice. Pelo menos é melhor do que usarIndexOf
.Você só precisa usar a sobrecarga de indexação de Select para agrupar cada item da coleção com um objeto anônimo que conhece o índice. Isso pode ser feito contra qualquer coisa que implemente IEnumerable.
fonte
Usando LINQ, C # 7 e o
System.ValueTuple
pacote NuGet, você pode fazer isso:Você pode usar a
foreach
construção regular e poder acessar o valor e o índice diretamente, não como membro de um objeto, e mantém os dois campos apenas no escopo do loop. Por esses motivos, acredito que esta é a melhor solução se você puder usar o C # 7 eSystem.ValueTuple
.fonte
Usando a resposta do @ FlySwat, eu vim com esta solução:
Você obtém o enumerador usando
GetEnumerator
e depois faz um loop usando umfor
loop. No entanto, o truque é criar a condição do looplistEnumerator.MoveNext() == true
.Como o
MoveNext
método de um enumerador retorna true se houver um próximo elemento e ele puder ser acessado, a condição do loop fará com que o loop pare quando ficarmos sem elementos para iterar.fonte
IDisposable
.Não há nada errado em usar uma variável de contador. De fato, se você usa
for
,foreach
while
oudo
, uma variável de contador deve em algum lugar ser declarada e incrementada.Portanto, use esse idioma se não tiver certeza se possui uma coleção indexada adequadamente:
Caso contrário, use este caso você saiba que sua coleção indexável é O (1) para acesso ao índice (para o qual será
Array
e provavelmenteList<T>
(a documentação não diz), mas não necessariamente para outros tipos (comoLinkedList
)):Nunca deve ser necessário operar 'manualmente'
IEnumerator
, invocandoMoveNext()
e interrogandoCurrent
-foreach
está poupando esse incômodo em particular ... se você precisar pular itens, basta usar umcontinue
no corpo do loop.E, para ser completo, dependendo do que você estava fazendo com o seu índice (as construções acima oferecem bastante flexibilidade), você pode usar o Parallel LINQ:
Usamos
AsParallel()
acima, porque já estamos em 2014 e queremos fazer bom uso desses múltiplos núcleos para acelerar as coisas. Além disso, para o LINQ 'sequencial', você sóForEach()
List<T>
Array
usa um método de extensão e ... e não está claro que usá-lo seja melhor do que fazer um simplesforeach
, pois você ainda está executando o thread único para obter uma sintaxe mais feia.fonte
Você pode agrupar o enumerador original com outro que contenha as informações do índice.
Aqui está o código para a
ForEachHelper
classe.fonte
Aqui está uma solução que eu acabei de encontrar para esse problema
Código original:
Código atualizado
Método de extensão:
fonte
Basta adicionar seu próprio índice. Mantenha simples.
fonte
Só vai funcionar para uma lista e não para qualquer IEnumerable, mas no LINQ existe o seguinte:
@ Jonathan Eu não disse que era uma ótima resposta, apenas disse que estava apenas mostrando que era possível fazer o que ele pediu :)
@ Graphain Eu não esperaria que fosse rápido - não tenho muita certeza de como funciona, poderia reiterar a lista inteira a cada vez para encontrar um objeto correspondente, o que seria uma enorme quantidade de comparações.
Dito isto, a Lista pode manter um índice de cada objeto junto com a contagem.
Jonathan parece ter uma idéia melhor, se ele elaborar?
Seria melhor apenas manter uma contagem de onde você está na frente, mais simples e mais adaptável.
fonte
O C # 7 finalmente nos fornece uma maneira elegante de fazer isso:
fonte
Por que foreach ?!
A maneira mais simples é usar for em vez de foreach se você estiver usando a Lista :
Ou se você deseja usar foreach:
Você pode usar isso para conhecer o índice de cada loop:
fonte
Isso funcionaria para o suporte a coleções
IList
.fonte
O(n^2)
uma vez na maioria das implementaçõesIndexOf
éO(n)
. 2) Isso falhará se houver itens duplicados na lista.É assim que eu faço, o que é bom por sua simplicidade / brevidade, mas se você estiver fazendo muito no corpo do loop
obj.Value
, ele envelhecerá rapidamente.fonte
Esta resposta: solicite à equipe de idiomas C # o suporte direto ao idioma.
A resposta principal afirma:
Embora isso seja verdade na versão atual do idioma C # (2020), esse não é um limite conceitual de CLR / idioma, mas pode ser feito.
A equipe de desenvolvimento de linguagem C # da Microsoft poderia criar um novo recurso de linguagem C #, adicionando suporte para um novo Interface IIndexedEnumerable
Se
foreach ()
for usado ewith var index
estiver presente, o compilador espera que a coleção de itens declareIIndexedEnumerable
interface. Se a interface estiver ausente, o compilador pode preencher a origem com um objeto IndexedEnumerable, que adiciona o código para rastrear o índice.Posteriormente, o CLR pode ser atualizado para ter um rastreamento de índice interno, usado apenas se a
with
palavra-chave for especificada e a origem não implementar diretamenteIIndexedEnumerable
Por quê:
Embora a maioria das pessoas aqui não seja funcionário da Microsoft, esta é uma resposta correta, você pode pressionar a Microsoft para adicionar esse recurso. Você já pode criar seu próprio iterador com uma função de extensão e usar tuplas , mas a Microsoft pode polvilhar o açúcar sintático para evitar a função de extensão.
fonte
Se a coleção for uma lista, você pode usar List.IndexOf, como em:
fonte
Melhor usar a
continue
construção segura de palavras-chave como estafonte
Você pode escrever seu loop assim:
Depois de adicionar o seguinte método struct e extensão.
O método struct e extension encapsula a funcionalidade Enumerable.Select.
fonte
Minha solução para esse problema é um método de extensão
WithIndex()
,http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs
Use-o como
fonte
struct
para o par (índice, item).Por interesse, Phil Haack acabou de escrever um exemplo disso no contexto de um Delegado de Navalha ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )
Ele efetivamente escreve um método de extensão que envolve a iteração em uma classe "IteratedItem" (veja abaixo), permitindo o acesso ao índice e ao elemento durante a iteração.
No entanto, embora isso seja bom em um ambiente que não seja o Razor, se você estiver executando uma única operação (ou seja, uma que possa ser fornecida como um lambda), não será uma substituição sólida da sintaxe de for / foreach em contextos que não sejam do Razor .
fonte
Eu não acho que isso deva ser bastante eficiente, mas funciona:
fonte
Eu construí isso no LINQPad :
Você também pode usar
string.join
:fonte
n
itens, as operações sãon * n
ou estão localizadasn
. pt.wikipedia.org/wiki/…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ê provavelmente faria uma de três coisas:
1) Obtendo o objeto da coleção, mas neste caso você já o possui.
2) Contando os objetos para posterior processamento posterior ... as coleções têm uma propriedade Count que você pode usar.
3) Configurando uma propriedade no objeto com base em sua ordem no loop ... embora você possa configurá-lo facilmente ao adicionar o objeto à coleção.
fonte
A menos que sua coleção possa retornar o índice do objeto por algum método, a única maneira é usar um contador como no seu exemplo.
No entanto, ao trabalhar com índices, a única resposta razoável para o problema é usar um loop for. Qualquer outra coisa introduz a complexidade do código, sem mencionar a complexidade do tempo e do espaço.
fonte
Que tal algo como isso? Observe que myDelimitedString pode ser nulo se myEnumerable estiver vazio.
fonte
Acabei de ter esse problema, mas pensar no problema no meu caso deu a melhor solução, não relacionada à solução esperada.
Pode ser um caso bastante comum, basicamente, estou lendo de uma lista de fontes e criando objetos com base neles em uma lista de destinos. No entanto, tenho que verificar se os itens de origem são válidos primeiro e quero retornar a linha de qualquer erro. À primeira vista, desejo inserir o índice no enumerador do objeto na propriedade Current, no entanto, ao copiar esses elementos, conheço implicitamente o índice atual de qualquer maneira, a partir do destino atual. Obviamente, depende do seu objeto de destino, mas para mim era uma Lista e provavelmente implementará o ICollection.
ie
Nem sempre é aplicável, mas muitas vezes vale a pena mencionar, eu acho.
Enfim, o ponto é que, às vezes, já existe uma solução não óbvia na lógica que você tem ...
fonte
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 #, geralmente você pode adaptar o método IEnumerable.Select para obter o índice do que desejar. Por exemplo, eu poderia usar algo assim para saber se um valor é ímpar ou par.
Isso daria a você um dicionário pelo nome, indicando se o item era ímpar (1) ou par (0) na lista.
fonte