Question Équivalent LINQ de foreach pour IEnumerable


Je voudrais faire l'équivalent de ce qui suit dans LINQ, mais je n'arrive pas à comprendre comment:

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

Quelle est la vraie syntaxe?


606
2017-10-14 09:56


origine


Réponses:


Il n'y a pas d'extension ForEach pour IEnumerable; seulement pour List<T>. Donc tu pourrais 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);
    }
}

733
2017-10-14 10:00



Fredrik a fourni le correctif, mais il peut être utile de considérer pourquoi ce n'est pas dans le cadre pour commencer. Je crois que l'idée est que les opérateurs de requête LINQ devraient être sans effets secondaires, s'insérant dans une manière raisonnablement fonctionnelle de regarder le monde. Clairement ForEach est exactement le contraire - un purement construction basée sur les effets secondaires.

Cela ne veut pas dire que c'est une mauvaise chose à faire - juste en pensant aux raisons philosophiques derrière la décision.


329
2017-10-14 10:10



Mettre à jour 17/07/2012: Apparemment à partir de C # 5.0, le comportement de foreach décrit ci-dessous a été changé et "l'utilisation d'un foreach Une variable d'itération 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 5.0, est qu'il est incompatible avec la façon dont l'équivalent "pour la compréhension" fonctionne dans d'autres langues, et avec la façon dont je m'attendrais à ce qu'il fonctionne (opinion personnelle énoncée ici seulement parce que d'autres ont mentionné leur opinion concernant la lisibilité). Voir toutes les questions concernant "Accès à la fermeture modifiée" aussi bien que "Fermeture sur la variable de boucle considérée comme nuisible"Ceci est seulement" nuisible "à cause de la façon dont" foreach "est implémenté 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);
        }
    }
}

Toutes mes excuses pour l'exemple trop artificiel. J'utilise seulement Observable parce que ce n'est pas complètement tiré par les cheveux pour faire quelque chose comme ça. De toute évidence, il existe de meilleurs moyens de créer cette observable, je tente seulement de démontrer un point. Généralement, le code souscrit à l'observable est exécuté de manière asynchrone et potentiellement dans un autre thread. Si vous utilisez "foreach", cela pourrait produire des résultats très étranges et potentiellement non déterministes.

Le test suivant utilisant la méthode d'extension "ForEach" passe:

[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()));
}

Ce qui suit échoue avec l'erreur:

Attendu: équivalent à <0, 1, 2, 3, 4, 5, 6, 7, 8, 9>    Mais était: <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()));
}

33
2017-09-01 19:24



Vous pouvez utiliser le FirstOrDefault() extension, qui est disponible pour IEnumerable<T>. En revenant false à partir du prédicat, il sera exécuté pour chaque élément mais ne se souciera pas qu'il ne trouve pas réellement de correspondance. Cela évitera le ToList() aérien.

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

31
2017-10-05 14:16



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

De cette façon, la méthode prend en charge exécution différée comme les autres méthodes LINQ.

MODIFIER: Si ce n'était pas clair, toute utilisation de cette méthode doit se terminer par ToList () ou tout autre moyen de forcer la méthode à travailler sur l'énumérable complet. Sinon, l'action ne serait pas effectué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 pour l'aider à le voir:

[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 supprimez le Lister() à la fin, vous verrez le test échouer puisque le StringBuilder contient une chaîne vide. C'est parce qu'aucune méthode n'a forcé le ForEach à énumérer.


19
2018-06-02 08:54



Gardez vos effets secondaires hors de mon IEnumerable

Je voudrais faire l'équivalent de ce qui suit dans LINQ, mais je n'arrive pas à comprendre comment:

Comme d'autres l'ont souligné ici et à l'étranger LINQ et IEnumerable les méthodes devraient être sans effets secondaires.

Voulez-vous vraiment "faire quelque chose" à chaque élément de l'IEnumerable? alors foreach est le meilleur choix. Les gens ne sont pas surpris quand des effets secondaires se produisent ici.

foreach (var i in items) i.DoStuff();

Je parie que tu ne veux pas d'effet secondaire

Cependant, selon mon expérience, les effets secondaires ne sont généralement pas nécessaires. Plus souvent qu'autrement, une simple requête LINQ attend d'être découverte accompagnée d'une réponse de StackOverflow.com par Jon Skeet, Eric Lippert ou Marc Gravell expliquant comment faire ce que vous voulez!

Quelques exemples

Si vous ne faites qu'agréger (accumuler) de la valeur, alors vous devriez considérer Aggregate méthode d'extension.

items.Aggregate(initial, (acc, x) => ComputeAccumulatedValue(acc, x));

Peut-être voulez-vous créer un nouveau IEnumerable à partir des valeurs existantes.

items.Select(x => Transform(x));

Ou peut-être que vous voulez créer une table de consultation:

items.ToLookup(x, x => GetTheKey(x))

La liste (jeu de mots pas entièrement prévu) des possibilités continue encore et encore.


10
2017-12-26 22:56



Il existe une version expérimentale par Microsoft de Extensions interactives de LINQ (aussi sur NuGetvoir Le profil de RxTeams pour plus de liens). le Vidéo Channel 9 l'explique bien.

Ses documents sont uniquement fournis au format XML. J'ai couru ça documentation à Sandcastle pour lui permettre d'être dans un format plus lisible. Décompressez l'archive de docs et recherchez index.html.

Parmi de nombreux autres goodies, il fournit l'implémentation prévue de ForEach. Cela vous permet d'écrire du code comme ceci:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };

numbers.ForEach(x => Console.WriteLine(x*x));

8
2017-08-15 15:49