849 votes

Équivalent LINQ de foreach pour IEnumerable<T>

J'aimerais faire l'équivalent de ce qui suit en LINQ, mais je n'arrive pas à comprendre comment :

IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());

Quelle est la véritable syntaxe ?

4 votes

@pst : J'avais l'habitude de coller aveuglément ces méthodes d'extension dans mes projets. Merci pour cet article.

3 votes

Il y a PlusLINQ qui dispose d'un ForEach extension .

0 votes

Voici une autre idée de la façon dont cette pourrait être possible : visualstudio.uservoice.com/forums/121579-visual-studio-2015/

1022voto

Fredrik Kalseth Points 6633

Il n'y a pas d'extension ForEach pour IEnumerable ; uniquement pour les List<T> . Vous pouvez donc faire

items.ToList().ForEach(i => i.DoStuff());

Vous pouvez également écrire votre propre méthode d'extension ForEach :

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

0 votes

J'avais une méthode de ce type dans mon code, mais en réalité ToList().ForEach() résout le problème et je pense que la réponse de Jon, ci-dessous, est plus correcte.

260 votes

Soyez prudent avec ToList(), car ToList() crée une copie de la séquence, ce qui peut entraîner des problèmes de performance et de mémoire.

17 votes

Il y a quelques raisons pour lesquelles ".Foreach()" est préférable. 1. Multi-threading, possible avec PLINQ. 2. Exceptions, vous pouvez vouloir collecter toutes les exceptions dans la boucle et les lancer en une seule fois ; et ce sont des codes de bruit.

418voto

Jon Skeet Points 692016

Fredrik a fourni le correctif, mais il peut être utile de se demander pourquoi cela ne figure pas dans le cadre dès le départ. Je pense que l'idée est que les opérateurs de requête LINQ devraient être sans effet de bord, s'intégrant dans une manière raisonnablement fonctionnelle de voir le monde. Il est clair que ForEach est exactement le contraire - un purement une construction basée sur les effets secondaires.

Cela ne veut pas dire qu'il s'agit d'une mauvaise chose à faire - il s'agit simplement de réfléchir aux raisons philosophiques qui sous-tendent cette décision.

3 votes

À titre de référence, cette question pose la même problématique et reçoit l'avis des "pouvoirs en place" : stackoverflow.com/questions/858978/

9 votes

Cependant, il y a une chose très utile que les fonctions LINQ fournissent typiquement et que foreach () ne fournit pas - la fonction anonyme prise par les fonctions Linq peut aussi prendre un index comme paramètre, alors qu'avec foreach vous devez reconstruire la construction classique for (int i..). Et les langages fonctionnels doivent permettre l'itération qui a des effets de bord - sinon vous ne pourriez jamais faire un iota de travail avec eux :) J'aimerais souligner que la méthode fonctionnelle typique consisterait à renvoyer l'énumérable original inchangé.

0 votes

@Walt : Si une méthode fonctionnelle typique renvoyait l'énumérable original et le n'a pas ont des effets secondaires (qui typique (les méthodes fonctionnelles ne le font pas), il n'y aurait alors aucun effet. Peut-être pourriez-vous clarifier votre commentaire ?

40voto

drstevens Points 1819

Mise à jour 7/17/2012 : Apparemment, à partir de C# 5.0, le comportement de foreach décrite ci-dessous a été modifiée et " l'utilisation d'un foreach dans une expression lambda imbriquée ne produit plus de résultats inattendus. " Cette réponse ne s'applique pas à C# 5.0.

@John Skeet et tous ceux qui préfèrent le mot-clé foreach.

Le problème avec "foreach" en C# avant la version 5.0 L'opinion personnelle exprimée ici, uniquement parce que d'autres ont mentionné leur opinion concernant la lisibilité, est qu'elle n'est pas cohérente avec la manière dont l'équivalent "pour la compréhension" fonctionne dans d'autres langues, et avec la manière dont je m'attendrais à ce qu'il fonctionne. Voir toutes les questions concernant " Accès à une fermeture modifiée " ainsi que " La fermeture de la variable de la boucle est considérée comme nuisible ". Cette situation n'est "préjudiciable" qu'en raison de la manière dont "foreach" est mis en œuvre en C#.

Prenez les exemples suivants en utilisant la méthode d'extension fonctionnellement équivalente à celle de la réponse de @Fredrik Kalseth.

public static class Enumerables
{
    public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
    {
        foreach (T item in @this)
        {
            action(item);
        }
    }
}

Je m'excuse pour cet exemple trop artificiel. Je n'utilise Observable que parce que ce n'est pas complètement farfelu de faire quelque chose comme ça. Il est évident qu'il y a de meilleures façons de créer cet observable, j'essaie seulement de démontrer un point. Typiquement, le code souscrit à l'observable est exécuté de manière asynchrone et potentiellement dans un autre thread. Si l'on utilise "foreach", cela pourrait produire des résultats très étranges et potentiellement non déterministes.

Le test suivant, qui utilise la méthode d'extension "ForEach", est réussi :

[Test]
public void ForEachExtensionWin()
{
    //Yes, I know there is an Observable.Range.
    var values = Enumerable.Range(0, 10);

    var observable = Observable.Create<Func<int>>(source =>
                            {
                                values.ForEach(value => 
                                    source.OnNext(() => value));

                                source.OnCompleted();
                                return () => { };
                            });

    //Simulate subscribing and evaluating Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Win
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}

L'opération suivante échoue avec l'erreur :

Attendu : équivalent à < 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 > Mais c'était le cas : < 9, 9, 9, 9, 9, 9, 9, 9, 9, 9 >

[Test]
public void ForEachKeywordFail()
{
    //Yes, I know there is an Observable.Range.
    var values = Enumerable.Range(0, 10);

    var observable = Observable.Create<Func<int>>(source =>
                            {
                                foreach (var value in values)
                                {
                                    //If you have resharper, notice the warning
                                    source.OnNext(() => value);
                                }
                                source.OnCompleted();
                                return () => { };
                            });

    //Simulate subscribing and evaluating Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Fail
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}

0 votes

Que dit l'avertissement de resharper ?

1 votes

@reggaeguitar Je n'ai pas utilisé C# depuis 7 ou 8 ans, depuis avant la sortie de C# 5.0, qui a changé le comportement décrit ici. A l'époque, il donnait cet avertissement stackoverflow.com/questions/1688465/ . Je doute qu'il soit encore en alerte sur ce point.

39voto

Rhames Points 355

Vous pouvez utiliser le FirstOrDefault() qui est disponible pour les IEnumerable<T> . En renvoyant false du prédicat, il sera exécuté pour chaque élément mais ne se souciera pas du fait qu'il ne trouve pas de correspondance. Cela permet d'éviter le problème de la ToList() de la tête.

IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });

13 votes

Je suis également d'accord. Il s'agit peut-être d'une petite astuce, mais à première vue, ce code n'est pas très clair, en tout cas par rapport à une boucle foreach standard.

33 votes

J'ai dû rétrograder parce que, bien que techniquement correct, votre futur moi et tous vos successeurs vous détesteront à jamais parce qu'au lieu de foreach(Item i in GetItems()) { i.DoStuff();} Vous avez pris plus de personnages et vous avez rendu les choses extrêmement confuses.

15 votes

Ok, mais probablement un hack plus lisible : items.All(i => { i.DoStuff(); return true; }

25voto

Dor Rotman Points 331

J'ai repris la méthode de Fredrik et j'ai modifié le type de retour.

De cette manière, la méthode prend en charge exécution différée comme les autres méthodes LINQ.

EDITAR: Si cela n'était pas clair, toute utilisation de cette méthode doit se terminer par ToList() ou tout autre moyen de forcer la méthode à fonctionner sur l'énumérable complet. Dans le cas contraire, l'action ne serait pas exécutée !

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
    foreach (T item in enumeration)
    {
        action(item);
        yield return item;
    }
}

Et voici le test qui permet de s'en rendre compte :

[Test]
public void TestDefferedExecutionOfIEnumerableForEach()
{
    IEnumerable<char> enumerable = new[] {'a', 'b', 'c'};

    var sb = new StringBuilder();

    enumerable
        .ForEach(c => sb.Append("1"))
        .ForEach(c => sb.Append("2"))
        .ToList();

    Assert.That(sb.ToString(), Is.EqualTo("121212"));
}

Si vous retirez le ToList() à la fin, vous verrez que le test échoue car le StringBuilder contient une chaîne vide. C'est parce qu'aucune méthode n'a forcé le ForEach à énumérer.

0 votes

Votre autre mise en œuvre de ForEach est intéressante, mais elle ne correspond pas au comportement de List.ForEach dont la signature est public void ForEach(Action<T> action) . Elle ne correspond pas non plus au comportement de la Observable.ForEach extension à IObservable<T> dont la signature est public static void ForEach<TSource>(this IObservable<TSource> source, Action<TSource> onNext) . A titre d'information, le ForEach équivalent aux collections Scala, même celles qui sont paresseuses, ont également un type de retour qui est équivalent à void en C#.

1 votes

C'est la meilleure réponse : exécution différée + api fluide.

4 votes

Cela fonctionne exactement de la même manière que l'appel à Select con ToList . L'objectif de la ForEach est de ne pas avoir à appeler ToList . Il doit être exécuté immédiatement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X