Não há nada errado em usar uma variável de contador. De fato, se você usa for, foreach whileou do, 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:
var i = 0;
foreach (var e in collection) {
// Do stuff with 'e' and 'i'
i++;
}
Caso contrário, use este caso você saiba que sua coleção indexável é O (1) para acesso ao índice (para o qual será Arraye provavelmente List<T>(a documentação não diz), mas não necessariamente para outros 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 deve ser necessário operar 'manualmente' IEnumerator, invocando MoveNext()e interrogando Current- foreachestá poupando esse incômodo em particular ... se você precisar pular itens, basta usar um continueno 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:
// 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()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 simples foreach, pois você ainda está executando o thread único para obter uma sintaxe mais feia.