C # foreach améliorations?

9

Je rencontre cela souvent pendant la programmation où je veux avoir un index de comptage de boucles à l'intérieur d'un foreach et devoir créer un entier, l'utiliser, l'incrémenter, etc. Ne serait-ce pas une bonne idée s'il y avait un mot-clé introduit qui était le nombre de boucles à l'intérieur d'un foreach? Il pourrait également être utilisé dans d'autres boucles.

Est-ce que cela va à l'encontre de l'utilisation de mots clés, car le compilateur ne permettrait pas que ce mot clé soit utilisé ailleurs que dans une construction en boucle?

Justin
la source
7
Vous pouvez le faire vous-même avec une méthode d'extension, voir la réponse de Dan Finch: stackoverflow.com/a/521894/866172 (pas la réponse la mieux notée, mais je pense que c'est la "plus propre")
Jalayn
Perl et des trucs comme $ _ me viennent à l'esprit. Je déteste ce genre de choses.
Yam Marcovic
2
D'après ce que je sais sur C #, il a de nombreux mots clés basés sur le contexte, donc cela ne "va pas à l'encontre de l'utilisation de mots clés". Comme indiqué dans une réponse ci-dessous, vous souhaiterez probablement simplement utiliser une for () boucle.
Craige
@Jalayn Veuillez poster cela comme réponse. C'est l'une des meilleures approches.
Apoorv Khurasia
@ MonsterTruck: Je ne pense pas qu'il devrait. La vraie solution serait de migrer cette question vers SO et de la fermer en tant que doublon de la question à laquelle Jalayn est lié.
Brian

Réponses:

54

Si vous avez besoin d'un nombre de boucles à l'intérieur d'une foreachboucle, pourquoi ne pas simplement utiliser une forboucle régulière . La foreachboucle était destinée à forsimplifier les utilisations spécifiques des boucles. Il semble que vous ayez une situation où la simplicité du foreachn'est plus bénéfique.

Ryathal
la source
1
+1 - bon point. Il est assez facile d'utiliser IEnumerable.Count ou object.length avec une boucle for régulière pour éviter tout problème avec la détermination du nombre d'éléments dans l'objet.
11
@ GlenH7: Selon l'implémentation, cela IEnumerable.Countpourrait être coûteux (ou impossible, s'il s'agit d'une seule énumération). Vous devez savoir quelle est la source sous-jacente avant de pouvoir le faire en toute sécurité.
Binary Worrier
2
-1: Bill Wagner en plus efficace C # décrit pourquoi il faut toujours en tenir à foreachplus for (int i = 0…). Il a écrit quelques pages mais je peux résumer en deux mots - optimisation du compilateur à l'itération. Ok, ce n'était pas deux mots.
Apoorv Khurasia
13
@MonsterTruck: quoi que Bill Wagner ait écrit, j'ai vu suffisamment de situations comme celle décrite par l'OP, où une forboucle donne un meilleur code lisible (et l'optimisation théorique du compilateur est souvent une optimisation prématurée).
Doc Brown
1
@DocBrown Ce n'est pas aussi simple qu'une optimisation prématurée. J'ai moi-même identifié des goulots d'étranglement dans le passé et les ai corrigés en utilisant cette approche (mais je dois admettre que je faisais face à des milliers d'objets dans la collection). J'espère publier bientôt un exemple de travail. En attendant, voici Jon Skeet . [Il dit que l'amélioration des performances ne sera pas significative mais cela dépend beaucoup de la collection et du nombre d'objets].
Apoorv Khurasia
37

Vous pouvez utiliser des types anonymes comme celui-ci

foreach (var item in items.Select((v, i) => new { Value = v, Index = i }))
{
    Console.WriteLine("Item value is {0} and index is {1}.", item.Value, item.Index);
}

Ceci est similaire à la enumeratefonction de Python

for i, v in enumerate(items):
    print v, i
Kris Harper
la source
+1 Oh ... ampoule. J'aime cette approche. Pourquoi n'y ai-je jamais pensé auparavant?
Phil
17
Celui qui préfère cela en C # à une boucle for classique a clairement une opinion étrange sur la lisibilité. Cela peut être une astuce intéressante, mais je n'autoriserais pas quelque chose comme ça dans le code de qualité de production. C'est beaucoup plus bruyant qu'une boucle classique et ne peut pas être compris aussi rapidement.
Falcon
4
@Falcon, c'est une alternative valide SI VOUS NE POUVEZ PAS UTILISER une ancienne mode pour la boucle (qui a besoin d'un compte). Voilà quelques collections d'objets que j'ai dû contourner ...
Fabricio Araujo
8
@FabricioAraujo: Certes, C # pour les boucles ne nécessite pas de comptage. Pour autant que je sache, ils sont exactement les mêmes que les C pour les boucles, ce qui signifie que vous pouvez utiliser n'importe quelle valeur booléenne pour terminer la boucle. Telles que la vérification de la fin de l'énumération lorsque MoveNext renvoie false.
Zan Lynx
1
Cela a l'avantage supplémentaire d'avoir tout le code pour gérer l'incrément au début du bloc foreach au lieu d'avoir à le trouver.
JeffO
13

J'ajoute simplement la réponse de Dan Finch ici, comme demandé.

Ne me donne pas de points, donne des points à Dan Finch :-)

La solution consiste à écrire la méthode d'extension générique suivante:

public static void Each<T>( this IEnumerable<T> ie, Action<T, int> action )
{
    var i = 0;
    foreach ( var e in ie ) action( e, i++ );
}

Qui est utilisé comme ça:

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );
Jalayn
la source
2
C'est assez intelligent, mais je pense que l'utilisation d'un normal pour est plus "lisible", surtout si quelqu'un qui finit par maintenir le code peut ne pas être un as dans .NET et pas trop familier avec le concept des méthodes d'extension . J'attrape toujours ce petit bout de code. :)
MetalMikester
1
Pourrait discuter de chaque côté de cela, et de la question d'origine - je suis d'accord avec l'intention des affiches, il y a des cas où foreach lit mieux mais un index est nécessaire, mais je suis toujours opposé à l'ajout de sucre syntaxique à une langue si ce n'est pas le cas grandement nécessaire - j'ai exactement la même solution dans mon propre code avec un nom différent - strings.ApplyIndexed <T> (......)
Mark Mullin
Je suis d'accord avec Mark Mullin, vous pouvez argumenter de chaque côté. Je pensais que c'était une utilisation assez intelligente des méthodes d'extension et cela correspondait à la question, alors je l'ai partagée.
Jalayn
5

Je peux me tromper à ce sujet, mais j'ai toujours pensé que le point principal de la boucle foreach était de simplifier l'itération à partir des jours STL de C ++, c'est-à-dire

for(std::stltype<typename>::iterator iter = stl_type_instance.begin(); iter != stl_type_instance.end(); iter++)
{
   //dereference iter to use each object.
}

comparer cela à l'utilisation par INET de IEnumerable dans foreach

foreach(TypeName instance in DescendentOfIEnumerable)
{
   //use instance to use the object.
}

ce dernier est beaucoup plus simple et évite bon nombre des pièges anciens. De plus, ils semblent suivre des règles presque identiques. Par exemple, vous ne pouvez pas modifier le contenu IEnumerable à l'intérieur de la boucle (ce qui est beaucoup plus logique si vous y pensez de la manière STL). Cependant, la boucle for a souvent un objectif logique différent de celui de l'itération.

Jonathan Henson
la source
4
+1: peu de gens se souviennent que foreach est essentiellement du sucre syntaxique sur le modèle d'itérateur.
Steven Evers
1
Eh bien, il y a des différences; la manipulation du pointeur là où elle est présente dans .NET est cachée assez profondément au programmeur, mais vous pouvez écrire une boucle for qui ressemble beaucoup à l'exemple C ++:for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
KeithS
@KeithS, pas si vous réalisez que tout ce que vous touchez dans .NET qui n'est pas un type structuré EST UN POINTEUR, juste avec une syntaxe différente (. Au lieu de ->). En fait, passer un type de référence à une méthode revient à le passer comme pointeur et le passer par référence, c'est le passer comme double pointeur. Même chose. Du moins, c'est la façon dont une personne dont la langue maternelle est le C ++ y pense. En quelque sorte, vous apprenez l'espagnol à l'école en réfléchissant à la façon dont la langue est liée syntaxiquement à votre propre langue maternelle.
Jonathan Henson
* type de valeur type non structuré. Désolé.
Jonathan Henson
2

Si la collection en question est un IList<T>, vous pouvez simplement utiliser foravec l'indexation:

for (int i = 0; i < collection.Count; i++)
{
    var item = collection[i];
    // …
}

Sinon, alors vous pourriez utiliser Select(), il a une surcharge qui vous donne l'index.

Si cela ne convient pas non plus, je pense que maintenir cet index manuellement est assez simple, ce ne sont que deux très courtes lignes de code. Créer un mot clé spécifiquement pour cela serait une exagération.

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}
svick
la source
+1, également, si vous utilisez un index exactement une fois dans la boucle, vous pouvez faire quelque chose comme foo(++i)ou foo(i++)selon l'index nécessaire.
Job
2

Si tout ce que vous savez, c'est que la collection est un IEnumerable, mais que vous devez garder une trace du nombre d'éléments que vous avez traités jusqu'à présent (et donc du total lorsque vous avez terminé), vous pouvez ajouter quelques lignes à une boucle for de base:

var coll = GetMyCollectionAsAnIEnumerable();
var idx = 0;
for(var e = coll.GetEnumerator(); e.MoveNext(); idx++)
{
   var elem = e.Current;

   //use elem and idx as you please
}

Vous pouvez également ajouter une variable d'index incrémentée à une foreach:

var i=0;
foreach(var elem in coll)
{
   //do your thing, then...
   i++;
}

Si vous souhaitez rendre ce look plus élégant, vous pouvez définir une ou deux méthodes d'extension pour "masquer" ces détails:

public static void ForEach<T>(this IEnumerable<T> input, Action<T> action)
{
    foreach(T elem in input)
        action(elem);
}

public static void ForEach<T>(this IEnumerable<T> input, Action<T, int> action)
{
    var idx = 0;
    foreach(T elem in input)
        action(elem, idx++); //post-increment happens after parameter-passing
}

//usage of the index-supporting method
coll.ForEach((e, i)=>Console.WriteLine("Element " + (i+1) + ": " + e.ToString()));
KeithS
la source
1

La portée fonctionne également si vous voulez garder le bloc propre des collisions avec d'autres noms ...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }
Geai
la source
-1

Pour cela, j'utilise une whileboucle. Les boucles avec forfonctionnent également, et beaucoup de gens semblent les préférer, mais j'aime seulement les utiliser foravec quelque chose qui aura un nombre fixe d'itérations. IEnumerables peuvent être générés au moment de l'exécution, donc, à mon avis, whileest plus logique. Appelez cela une directive de codage informelle, si vous le souhaitez.

Robotman
la source