web-dev-qa-db-fra.com

LINQ équivalent de foreach pour IEnumerable <T>

J'aimerais faire l'équivalent de ce qui suit dans LINQ, mais je ne vois pas comment:

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

Quelle est la vraie syntaxe?

645
tags2k

Il n'y a pas d'extension ForEach pour IEnumerable; seulement pour List<T>. Alors tu pourrais faire

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

Sinon, écrivez 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);
    }
}
782
Fredrik Kalseth

Fredrik a fourni le correctif, mais il peut être utile de se demander pourquoi cela ne fait pas partie du cadre pour commencer. Je crois que l’idée est que les opérateurs de requête LINQ devraient être exempts d’effets secondaires et s’intégrer dans une vision du fonctionnement raisonnablement fonctionnelle. Clairement, ForEach est exactement le contraire: une construction basée sur les effets secondaires purement.

Cela ne veut pas dire que c'est une mauvaise chose à faire - il suffit de penser aux raisons philosophiques de la décision.

340
Jon Skeet

Update 7/17/2012: Apparemment, à partir de la version 5.0, le comportement de foreach décrit ci-dessous a été modifié et " l'utilisation d'une variable d'itération 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 de "foreach" en C # avant la version 5.0, c’est qu’il est incompatible avec le fonctionnement de l’équivalent "pour la compréhension" dans d’autres langues et avec la manière dont je compte le faire fonctionner (opinion personnelle). indiqué ici uniquement parce que d’autres ont mentionné leur opinion en matière de lisibilité). Voir toutes les questions concernant " Accès à la fermeture modifiée " Ainsi que " Variable de fermeture sur la boucle considérée comme préjudiciable ". 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 car ce n'est pas tout à fait farfelu de faire quelque chose comme ça. De toute évidence, il existe de meilleures façons de créer cet observable, je ne fais que tenter de démontrer un point. En règle générale, 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" réussit:

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

Vous pouvez utiliser l'extension FirstOrDefault(), disponible pour 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 évitera la surcharge ToList().

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

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

De cette façon, la méthode supporte deferred execution comme les autres méthodes LINQ.

EDIT: Si ce n'est 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. 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 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 ToList () , le test échouera car le StringBuilder contient une chaîne vide. En effet, aucune méthode n'a obligé ForEach à énumérer.

19
Dor Rotman

Gardez vos effets secondaires hors de mon IEnumerable

J'aimerais faire l'équivalent de ce qui suit dans LINQ, mais je ne vois pas comment:

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

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

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

Je parie que vous ne voulez pas d'effet secondaire

Cependant, d'après mon expérience, les effets secondaires ne sont généralement pas nécessaires. Le plus souvent, 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) une valeur, vous devez envisager la méthode d'extension Aggregate.

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

Peut-être souhaitez-vous créer une nouvelle variable IEnumerable à partir des valeurs existantes. 

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

Ou peut-être souhaitez-vous créer une table de correspondance:

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

La liste des possibilités (jeu de mots qui n’est pas tout à fait prévu) s’allonge encore et encore. 

16
cdiggins

Si vous voulez jouer le rôle d'énumération, vous devez donner chaque élément.

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

Microsoft a publié une version expérimentale de Interactive Extensions to LINQ (également sur NuGet , voir Profil de RxTeams pour plus de liens). La Vidéo du canal 9 l'explique bien.

Ses documents sont uniquement fournis au format XML. J'ai exécuté cette documentation dans Sandcastle pour lui permettre d'être dans un format plus lisible. Décompressez l’archive docs et cherchez index.html.

Parmi de nombreux autres avantages, il fournit l'implémentation attendue 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
John Wigger

Selon PLINQ (disponible depuis .Net 4.0), vous pouvez effectuer une

IEnumerable<T>.AsParallel().ForAll() 

faire une boucle foreach parallèle sur un IEnumerable.

7
Wolf5

Le but de ForEach est de provoquer des effets secondaires. IEnumerable est pour l'énumération paresseuse d'un ensemble.

Cette différence conceptuelle est assez visible quand on la considère.

SomeEnumerable.ForEach(item=>DataStore.Synchronize(item));

Cela ne fonctionnera que si vous faites un "compte" ou un "ToList ()" ou quelque chose dessus. Ce n'est clairement pas ce qui est exprimé.

Vous devez utiliser les extensions IEnumerable pour configurer des chaînes d'itération, définir le contenu en fonction de leurs sources et conditions respectives. Les arbres d'expression sont puissants et efficaces, mais vous devez apprendre à en apprécier la nature. Et pas seulement pour la programmation autour d’eux, pour sauver quelques caractères avant l’évaluation paresseuse.

6
Tormod

Beaucoup de gens l'ont mentionné, mais je devais l'écrire. N'est-ce pas le plus clair/le plus lisible?

IEnumerable<Item> items = GetItems();
foreach (var item in items) item.DoStuff();

Court et simple (st).

4
Nenad

Comme de nombreuses réponses le soulignent déjà, vous pouvez facilement ajouter vous-même une telle méthode d'extension. Cependant, si vous ne voulez pas faire cela, bien que je ne sache rien de ce genre dans la BCL, il existe toujours une option dans l'espace de noms System, si vous avez déjà une référence à Reactive Extension (et si vous ne le faites pas, vous devriez avoir):

using System.Reactive.Linq;

items.ToObservable().Subscribe(i => i.DoStuff());

Bien que les noms de méthodes soient un peu différents, le résultat final correspond exactement à ce que vous recherchez.

4
Mark Seemann

Maintenant, nous avons l'option de ...

        ParallelOptions parallelOptions = new ParallelOptions();
        parallelOptions.MaxDegreeOfParallelism = 4;
#if DEBUG
        parallelOptions.MaxDegreeOfParallelism = 1;
#endif
        Parallel.ForEach(bookIdList, parallelOptions, bookID => UpdateStockCount(bookID));

Bien sûr, cela ouvre une nouvelle boîte de nématodes.

ps (désolé pour les polices, c'est ce que le système a décidé)

2
Paulustrious

Encore un autre exemple ForEach

public static IList<AddressEntry> MapToDomain(IList<AddressModel> addresses)
{
    var workingAddresses = new List<AddressEntry>();

    addresses.Select(a => a).ToList().ForEach(a => workingAddresses.Add(AddressModelMapper.MapToDomain(a)));

    return workingAddresses;
}
1
neil martin

Inspiré par Jon Skeet, j'ai étendu sa solution avec les éléments suivants:

Méthode d'extension:

public static void Execute<TSource, TKey>(this IEnumerable<TSource> source, Action<TKey> applyBehavior, Func<TSource, TKey> keySelector)
{
    foreach (var item in source)
    {
        var target = keySelector(item);
        applyBehavior(target);
    }
}

Client:

var jobs = new List<Job>() 
    { 
        new Job { Id = "XAML Developer" }, 
        new Job { Id = "Assassin" }, 
        new Job { Id = "Narco Trafficker" }
    };

jobs.Execute(ApplyFilter, j => j.Id);

...

    public void ApplyFilter(string filterId)
    {
        Debug.WriteLine(filterId);
    }
1
Scott Nimrod

Je ne suis pas du tout d'accord avec l'idée que les méthodes d'extension de lien devraient être exemptes d'effets secondaires (non seulement parce qu'elles ne le sont pas, aucun délégué ne peut avoir d'effets secondaires).

Considérer ce qui suit:

   public class Element {}

   public Enum ProcessType
   {
      This = 0, That = 1, SomethingElse = 2
   }

   public class Class1
   {
      private Dictionary<ProcessType, Action<Element>> actions = 
         new Dictionary<ProcessType,Action<Element>>();

      public Class1()
      {
         actions.Add( ProcessType.This, DoThis );
         actions.Add( ProcessType.That, DoThat );
         actions.Add( ProcessType.SomethingElse, DoSomethingElse );
      }

      // Element actions:

      // This example defines 3 distict actions
      // that can be applied to individual elements,
      // But for the sake of the argument, make
      // no assumption about how many distict
      // actions there may, and that there could
      // possibly be many more.

      public void DoThis( Element element )
      {
         // Do something to element
      }

      public void DoThat( Element element )
      {
         // Do something to element
      }

      public void DoSomethingElse( Element element )
      {
         // Do something to element
      }

      public void Apply( ProcessType processType, IEnumerable<Element> elements )
      {
         Action<Element> action = null;
         if( ! actions.TryGetValue( processType, out action ) )
            throw new ArgumentException("processType");
         foreach( element in elements ) 
            action(element);
      }
   }

L’exemple montre bien qu’il s’agit en réalité d’une sorte de liaison tardive qui permet d’invoquer une des nombreuses actions possibles ayant des effets secondaires sur une séquence d’éléments, sans avoir à écrire une construction de commutateur volumineuse pour décoder la valeur qui définit l’action et la traduire. dans sa méthode correspondante.

0
caddzooks

MoreLinq a IEnumerable<T>.ForEach et une tonne d'autres extensions utiles. Cela ne vaut probablement pas la peine de dépendre juste pour ForEach, mais il contient beaucoup de choses utiles.

https://www.nuget.org/packages/morelinq/

https://github.com/morelinq/MoreLINQ

0
solublefish

Pour rester fluide on peut utiliser un tel truc:

GetItems()
    .Select(i => new Action(i.DoStuf)))
    .Aggregate((a, b) => a + b)
    .Invoke();
0
Alex

Pour VB.NET, vous devez utiliser:

listVariable.ForEach(Sub(i) i.Property = "Value")
0
Israel Margulies

Cette abstraction "approche fonctionnelle" fuit beaucoup de temps. Rien au niveau de la langue n'empêche les effets secondaires. Tant que vous pouvez le faire appeler votre délégué lambda/pour chaque élément du conteneur, vous obtiendrez le comportement "ForEach".

Voici par exemple un moyen de fusionner srcDictionary dans destDictionary (si la clé existe déjà - remplace)

ceci est un hack, et ne devrait pas être utilisé dans aucun code de production.

var b = srcDictionary.Select(
                             x=>
                                {
                                  destDictionary[x.Key] = x.Value;
                                  return true;
                                }
                             ).Count();
0
Zar Shardan