Existe-t-il une construction de langage rare que je n'ai pas rencontrée (comme celles que j'ai apprises récemment, certaines sur Stack Overflow) en C # pour obtenir une valeur représentant l'itération actuelle d'une boucle foreach?
Par exemple, je fais actuellement quelque chose comme ça selon les circonstances:
int i = 0;
foreach (Object o in collection)
{
// ...
i++;
}
foreach
est de fournir un mécanisme d'itération commun à toutes les collections, qu'elles soient indexables (List
) ou non (Dictionary
).Dictionary
ne soit pas indexable, une itération de leDictionary
traverse dans un ordre particulier (c'est-à-dire qu'un énumérateur est indexable par le fait qu'il produit des éléments séquentiellement). En ce sens, nous pourrions dire que nous ne recherchons pas l'index dans la collection, mais plutôt l'index de l'élément énuméré actuel dans l'énumération (c'est-à-dire si nous sommes au premier ou au cinquième ou au dernier élément énuméré).Réponses:
Le
foreach
sert à itérer sur les collections qui implémententIEnumerable
. Il le fait en appelantGetEnumerator
la collection, qui retournera unEnumerator
.Cet énumérateur a une méthode et une propriété:
Current
renvoie l'objet sur lequel Enumerator se trouve actuellement,MoveNext
metCurrent
à jour l'objet suivant.Le concept d'index est étranger au concept d'énumération et ne peut être fait.
Pour cette raison, la plupart des collections peuvent être parcourues à l'aide d'un indexeur et de la construction de boucle for.
Je préfère grandement utiliser une boucle for dans cette situation que de suivre l'index avec une variable locale.
la source
for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
zip
Ian Mercer a publié une solution similaire à celle-ci sur le blog de Phil Haack :
Cela vous donne l'élément (
item.value
) et son index (item.i
) en utilisant cette surcharge de LINQSelect
:Le
new { i, value }
crée un nouvel objet anonyme .Les allocations de tas peuvent être évitées en utilisant
ValueTuple
si vous utilisez C # 7.0 ou version ultérieure:Vous pouvez également éliminer le
item.
en utilisant la déstructuration automatique:la source
Enfin, C # 7 a une syntaxe décente pour obtenir un index à l'intérieur d'une
foreach
boucle (c'est-à-dire des tuples):Une petite méthode d'extension serait nécessaire:
la source
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
Enumerated
pour qu'elle soit plus reconnaissable pour les personnes habituées à d' autres langues (et peut-être aussi changer l'ordre des paramètres de tuple). CeWithIndex
n'est pas évident de toute façon.Pourrait faire quelque chose comme ceci:
la source
values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
Je ne suis pas d'accord avec les commentaires selon lesquels une
for
boucle est un meilleur choix dans la plupart des cas.foreach
est une construction utile, et non remplaçable par unefor
boucle en toutes circonstances.Par exemple, si vous avez un DataReader et parcourez tous les enregistrements à l'aide d'un
foreach
il appelle automatiquement la méthode Dispose et ferme le lecteur (qui peut ensuite fermer automatiquement la connexion). Ceci est donc plus sûr car il empêche les fuites de connexion même si vous oubliez de fermer le lecteur.(Bien sûr, il est recommandé de toujours fermer les lecteurs, mais le compilateur ne le détectera pas si vous ne le faites pas - vous ne pouvez pas garantir que vous avez fermé tous les lecteurs, mais vous pouvez augmenter la probabilité de ne pas fuir les connexions en obtenant l'habitude d'utiliser foreach.)
Il peut y avoir d'autres exemples de l'appel implicite de la
Dispose
méthode utile.la source
foreach
différencefor
(et le plus prèswhile
) avec Programmers.SE .Réponse littérale - avertissement, les performances peuvent ne pas être aussi bonnes que l'utilisation d'un
int
pour suivre l'index. Au moins, c'est mieux que d'utiliserIndexOf
.Il vous suffit d'utiliser la surcharge d'indexation de Select pour envelopper chaque élément de la collection avec un objet anonyme qui connaît l'index. Cela peut être fait contre tout ce qui implémente IEnumerable.
la source
En utilisant LINQ, C # 7 et le
System.ValueTuple
package NuGet, vous pouvez faire ceci:Vous pouvez utiliser la
foreach
construction régulière et être en mesure d'accéder directement à la valeur et à l'index, pas en tant que membre d'un objet, et ne conserve les deux champs que dans la portée de la boucle. Pour ces raisons, je pense que c'est la meilleure solution si vous pouvez utiliser C # 7 etSystem.ValueTuple
.la source
En utilisant la réponse de @ FlySwat, j'ai trouvé cette solution:
Vous obtenez l'énumérateur en utilisant
GetEnumerator
puis vous boucle en utilisant unefor
boucle. Cependant, l'astuce consiste à créer l'état de la bouclelistEnumerator.MoveNext() == true
.Étant donné que la
MoveNext
méthode d'un énumérateur renvoie true s'il existe un élément suivant et qu'il est accessible, ce qui fait que la condition de boucle arrête la boucle lorsque nous manquons d'éléments pour itérer.la source
IDisposable
.Il n'y a rien de mal à utiliser une variable de compteur. En fait, si vous utilisez
for
,foreach
while
oudo
, une variable de compteur doit être quelque part déclarée et incrémenté.Utilisez donc cet idiome si vous n'êtes pas sûr d'avoir une collection correctement indexée:
Sinon, utilisez celui-ci si vous savez que votre collection indexable est O (1) pour l'accès à l'index (ce sera pour
Array
et probablement pourList<T>
(la documentation ne le dit pas), mais pas nécessairement pour d'autres types (commeLinkedList
)):Il ne devrait jamais être nécessaire de faire fonctionner `` manuellement '' le
IEnumerator
en invoquantMoveNext()
et en l'interrogeantCurrent
-foreach
cela vous évite cette peine particulière ... si vous devez sauter des éléments, utilisez simplement uncontinue
dans le corps de la boucle.Et juste pour être complet, selon ce que vous faisiez avec votre index (les constructions ci-dessus offrent beaucoup de flexibilité), vous pouvez utiliser Parallel LINQ:
Nous utilisons
AsParallel()
ci-dessus, car c'est déjà 2014, et nous voulons faire bon usage de ces multiples cœurs pour accélérer les choses. De plus, pour LINQ `` séquentiel '', vous obtenez uniquement uneForEach()
méthode d'extensionList<T>
etArray
... et il n'est pas clair que l'utiliser soit mieux que de faire un simpleforeach
, car vous utilisez toujours un seul thread pour une syntaxe plus laide.la source
Vous pouvez envelopper l'énumérateur d'origine avec un autre qui contient les informations d'index.
Voici le code de la
ForEachHelper
classe.la source
Voici une solution que je viens de trouver pour ce problème
Code d'origine:
Code mis à jour
Méthode d'extension:
la source
Ajoutez simplement votre propre index. Rester simple.
la source
Cela ne fonctionnera que pour une liste et non pour un IEnumerable, mais dans LINQ, il y a ceci:
@Jonathan Je n'ai pas dit que c'était une excellente réponse, j'ai juste dit que cela montrait simplement qu'il était possible de faire ce qu'il avait demandé :)
@Graphain Je ne m'attendrais pas à ce que ce soit rapide - je ne sais pas exactement comment cela fonctionne, il pourrait réitérer à travers la liste entière à chaque fois pour trouver un objet correspondant, ce qui serait un helluvalot de comparaisons.
Cela dit, List peut conserver un index de chaque objet avec le nombre.
Jonathan semble avoir une meilleure idée, s'il voulait élaborer?
Il serait préférable de simplement compter où vous en êtes dans le foreach, plus simple et plus adaptable.
la source
C # 7 nous donne enfin une manière élégante de le faire:
la source
Pourquoi foreach?!
La manière la plus simple est d'utiliser for au lieu de foreach si vous utilisez List :
Ou si vous souhaitez utiliser foreach:
Vous pouvez l'utiliser pour connaître l'index de chaque boucle:
la source
Cela fonctionnerait pour les collections soutenant
IList
.la source
O(n^2)
dans la plupart des implémentations,IndexOf
c'estO(n)
. 2) Cela échoue s'il y a des éléments en double dans la liste.C'est comme ça que je le fais, ce qui est bien pour sa simplicité / brièveté, mais si vous faites beaucoup de choses dans le corps de la boucle
obj.Value
, ça va vieillir assez rapidement.la source
Cette réponse: faites pression sur l'équipe de langage C # pour une prise en charge linguistique directe.
La réponse principale indique:
Bien que cela soit vrai de la version actuelle du langage C # (2020), ce n'est pas une limite conceptuelle CLR / langage, cela peut être fait.
L'équipe de développement du langage Microsoft C # pourrait créer une nouvelle fonctionnalité du langage C #, en ajoutant la prise en charge d'une nouvelle interface IIndexedEnumerable
Si
foreach ()
est utilisé etwith var index
est présent, le compilateur attend que la collection d'éléments déclare l'IIndexedEnumerable
interface. Si l'interface est absente, le compilateur peut encapsuler polyfill la source avec un objet IndexedEnumerable, qui ajoute le code de suivi de l'index.Plus tard, le CLR peut être mis à jour pour avoir un suivi d'index interne, qui n'est utilisé que si le
with
mot clé est spécifié et que la source n'implémente pas directementIIndexedEnumerable
Pourquoi:
Bien que la plupart des gens ici ne soient pas des employés de Microsoft, c'est une bonne réponse, vous pouvez faire pression sur Microsoft pour ajouter une telle fonctionnalité. Vous pouvez déjà créer votre propre itérateur avec une fonction d'extension et utiliser des tuples , mais Microsoft peut saupoudrer le sucre syntaxique pour éviter la fonction d'extension
la source
Si la collection est une liste, vous pouvez utiliser List.IndexOf, comme dans:
la source
Mieux vaut utiliser
continue
une construction sécurisée par mots clés comme celle-cila source
Vous pouvez écrire votre boucle comme ceci:
Après avoir ajouté la structure et la méthode d'extension suivantes.
La méthode struct et d'extension encapsule la fonctionnalité Enumerable.Select.
la source
Ma solution à ce problème est une méthode d'extension
WithIndex()
,http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs
Utilisez-le comme
la source
struct
pour la paire (index, élément).Par intérêt, Phil Haack vient d'écrire un exemple de cela dans le contexte d'un délégué modèle Razor ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )
En effet, il écrit une méthode d'extension qui enveloppe l'itération dans une classe "IteratedItem" (voir ci-dessous) permettant l'accès à l'index ainsi qu'à l'élément pendant l'itération.
Cependant, bien que ce soit bien dans un environnement non-Razor si vous effectuez une seule opération (c'est-à-dire une opération qui pourrait être fournie en tant que lambda), cela ne remplacera pas solidement la syntaxe for / foreach dans des contextes non-Razor. .
la source
Je ne pense pas que cela devrait être assez efficace, mais cela fonctionne:
la source
J'ai construit ceci dans LINQPad :
Vous pouvez également simplement utiliser
string.join
:la source
n
éléments, les opérations le sontn * n
ou aun
carré. en.wikipedia.org/wiki/…Je ne crois pas qu'il existe un moyen d'obtenir la valeur de l'itération actuelle d'une boucle foreach. Se compter, semble être le meilleur moyen.
Puis-je vous demander pourquoi vous voudriez savoir?
Il semble que vous feriez le plus probablement l'une des trois choses suivantes:
1) Récupérer l'objet de la collection, mais dans ce cas, vous l'avez déjà.
2) Comptage des objets pour un post-traitement ultérieur ... les collections ont une propriété Count que vous pouvez utiliser.
3) Définir une propriété sur l'objet en fonction de son ordre dans la boucle ... bien que vous puissiez facilement le définir lorsque vous avez ajouté l'objet à la collection.
la source
À moins que votre collection ne puisse retourner l'index de l'objet via une méthode, la seule façon est d'utiliser un compteur comme dans votre exemple.
Cependant, lorsque vous travaillez avec des index, la seule réponse raisonnable au problème consiste à utiliser une boucle for. Tout autre élément introduit la complexité du code, sans parler de la complexité du temps et de l'espace.
la source
Que diriez-vous quelque chose comme ça? Notez que myDelimitedString peut être null si myEnumerable est vide.
la source
Je viens d'avoir ce problème, mais réfléchir au problème dans mon cas a donné la meilleure solution, sans rapport avec la solution attendue.
Cela pourrait être un cas assez courant, en gros, je lis à partir d'une liste source et crée des objets basés sur eux dans une liste de destination, cependant, je dois vérifier si les éléments source sont valides en premier et vouloir retourner la ligne de tout Erreur. À première vue, je veux obtenir l'index dans l'énumérateur de l'objet à la propriété Current, cependant, comme je copie ces éléments, je connais implicitement l'index actuel de toute façon à partir de la destination actuelle. Évidemment, cela dépend de votre objet de destination, mais pour moi, c'était une liste, et très probablement il implémentera ICollection.
c'est à dire
Pas toujours applicable, mais assez souvent pour être mentionné, je pense.
Quoi qu'il en soit, le fait est qu'il existe parfois une solution non évidente déjà dans la logique que vous avez ...
la source
Je n'étais pas sûr de ce que vous essayiez de faire avec les informations d'index basées sur la question. Cependant, en C #, vous pouvez généralement adapter la méthode IEnumerable.Select pour extraire l'index de tout ce que vous voulez. Par exemple, je pourrais utiliser quelque chose comme ça pour savoir si une valeur est impaire ou paire.
Cela vous donnerait un dictionnaire par nom pour savoir si l'élément était impair (1) ou même (0) dans la liste.
la source