Equivalent LINQ de foreach pour IEnumerable

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

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

Quelle est la vraie syntaxe?

616
demandé sur abatishchev 2008-10-14 13:56:09

20 réponses

il n'y a pas D'extension ForEach pour IEnumerable ; seulement pour List<T> . Donc vous pouvez faire

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

alternativement, é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);
    }
}
744
répondu Fredrik Kalseth 2015-06-29 08:37:48

Fredrik a fourni le correctif, mais il peut être intéressant d'examiner pourquoi ce n'est pas dans le cadre pour commencer. Je pense que L'idée est que les opérateurs de requêtes LINQ devraient être sans effets secondaires, s'inscrivant dans une façon raisonnablement fonctionnelle de regarder le monde. De toute évidence, ForEach est exactement le contraire - une construction purement effet secondaire basé.

cela ne veut pas dire que c'est une mauvaise chose à faire - juste en pensant aux raisons philosophiques l'origine de la décision.

330
répondu Jon Skeet 2008-10-14 10:10:51

Update 7/17/2012: apparemment à partir de C# 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 tout le monde qui préfère le mot-clé foreach.

le problème de La "foreach" en C# avant 5.0 , c'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 exprimé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 " ainsi que" fermeture sur la variable boucle considérée comme nuisible ". C'est seulement "nocif" en raison de la façon dont" foreach " est mis en œuvre dans 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);
        }
    }
}

mes excuses pour l'exemple exagérément artificiel. J'utilise seulement Observable parce que ce n'est pas complètement farfelu de faire quelque chose comme ça. Évidemment, il y a de meilleures façons de créer cette observable, j'essaie seulement de démontrer un point. Typiquement le code souscrit à l'observable est exécuté asynchrone et potentiellement dans un autre thread. Si on utilise "foreach", cela pourrait produire des résultats très étranges et potentiellement non déterministes.

l'essai suivant utilisant 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()));
}

échoue avec l'erreur:

prévu: é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
répondu drstevens 2017-05-23 11:47:29

vous pouvez utiliser l'extension FirstOrDefault() , qui est disponible pour IEnumerable<T> . En retournant false du prédicat, il sera exécuté pour chaque élément mais ne se souciera pas qu'il ne trouve pas réellement une correspondance. On évitera ainsi le ToList() .

IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });
31
répondu Rhames 2013-07-12 10:39:40

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

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

EDIT: Si ce n'était pas clair, toute utilisation de cette méthode doit se terminer par ToList() ou de tout autre moyen de forcer la méthode de travail sur l'ensemble énumérable. Sinon, 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 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 () à 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
répondu Dor Rotman 2010-06-03 16:39:27

Gardez vos Effets Secondaires de mon IEnumerable

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

comme d'autres l'ont souligné ici et à L'étranger LINQ et IEnumerable méthodes devraient être sans effet secondaire.

voulez-vous vraiment "faire quelque chose" à chaque élément dans le 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 vous ne voulez pas qu'un effet de bord

cependant, d'après mon expérience, les effets secondaires ne sont généralement pas nécessaires. Plus souvent qu'autrement, il y a une simple requête LINQ qui attend d'être découverte accompagnée d'un StackOverflow.com répondez par Jon Skeet, Eric Lippert, ou Marc Gravell expliquant comment faire ce que vous voulez!

quelques exemples

si vous Agrégez (accumulez) juste une valeur, alors vous devriez considérer la méthode d'extension Aggregate .

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 voulez-vous créer une table de recherche:

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

la liste (Jeu de mots pas entièrement prévu) des possibilités continue et continue.

13
répondu cdiggins 2017-05-23 11:54:59

il y a une version expérimentale par Microsoft de Extensions interactives à LINQ (aussi sur NuGet , voir profil de RxTeams pour plus de liens). Le Canal 9 vidéo explique bien.

ses documents ne sont fournis qu'en format XML. J'ai lancé cette documentation dans Sandcastle pour la rendre plus lisible. Décompressez les docs archivez et recherchez l'index .html .

parmi bien d'autres, il fournit la mise en œuvre attendue. Il vous permet d'écrire le code comme ceci:

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

numbers.ForEach(x => Console.WriteLine(x*x));
8
répondu John Wigger 2014-12-06 04:27:51

si vous voulez agir comme les rouleaux d'énumération, vous devez donner chaque article.

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;
        }
    }
}
7
répondu regisbsb 2015-07-31 16:04:53

selon PLINQ (disponible depuis .Net 4.0), vous pouvez faire un

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

pour faire une boucle foreach parallèle sur un IEnumerable.

7
répondu Wolf5 2016-03-17 14:10:26

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 vous considérez.

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

ceci ne s'exécute pas jusqu'à ce que vous fassiez un" compte "ou un" ToList () " ou quelque chose dessus. Il n'est clairement pas ce qui est exprimé.

vous devez utiliser les extensions IEnumerable pour configurer les chaînes d'itération, définition du contenu par leurs sources et conditions respectives. Les arbres d'Expression sont puissants et efficaces, mais vous devriez apprendre à apprécier leur nature. Et pas seulement pour programmer autour d'eux pour sauver quelques caractères qui supplantent l'évaluation paresseuse.

5
répondu Tormod 2010-09-01 08:46:38

comme de nombreuses réponses l'indiquent déjà, vous pouvez facilement ajouter une telle méthode d'extension vous-même. Cependant, si vous ne voulez pas faire cela, bien que je ne sois pas au courant de quelque chose comme ça dans la BCL, il y a toujours une option dans l'espace de nom System , si vous avez déjà une référence à extension réactive (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éthode soient un peu différents, le résultat final est exactement ce que vous êtes à la recherche pour.

4
répondu Mark Seemann 2014-01-05 20:27:29

beaucoup de gens l'ont mentionné, mais j'ai dû l'écrire. N'est-ce pas plus clair/plus lisible?

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

Court et simple(st).

3
répondu Nenad 2012-07-08 22:47:32

maintenant nous avons le choix 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 de caractères, c'est ce que le système choisi)

2
répondu Paulustrious 2011-06-05 17:47:48

inspiré par Jon Skeet, j'ai étendu sa solution avec ce qui suit:

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
répondu Scott Nimrod 2014-09-11 00:45:30

ForEach peut aussi être enchaîné , juste remis à la pileline après l'action. reste fluide


Employees.ForEach(e=>e.Act_A)
         .ForEach(e=>e.Act_B)
         .ForEach(e=>e.Act_C);

Orders  //just for demo
    .ForEach(o=> o.EmailBuyer() )
    .ForEach(o=> o.ProcessBilling() )
    .ForEach(o=> o.ProcessShipping());


//conditional
Employees
    .ForEach(e=> {  if(e.Salary<1000) e.Raise(0.10);})
    .ForEach(e=> {  if(e.Age   >70  ) e.Retire();});

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enu, Action<T> action)
{
    foreach (T item in enu) action(item);
    return enu; // make action Chainable/Fluent
}

Edit2 le code ci-dessus fonctionne, mais une version meilleure utilise cette .

Edit ci-dessous était un mauvais exemple, souligné par Taemyr. Merci beaucoup.

Employees.ForEach(e=>e.Salary = e.Salary * 2)
         .Where (e=> e.Salary > 10000)
         .Average(e=> e.Salary);

1
répondu Rm558 2017-05-23 12:34:44

je suis respectueusement en désaccord avec la notion que les méthodes d'extension de lien devrait être sans effet secondaire (non seulement parce qu'ils ne sont pas, tout délégué peut effectuer des 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);
      }
   }

ce que l'exemple montre est vraiment juste une sorte de late-binding qui permet d'invoquer l'une des nombreuses actions possibles ayant des effets secondaires sur une séquence d'éléments, sans avoir à écrire un grand commutateur construire pour décoder le valeur qui définit l'action et la traduit dans sa méthode correspondante.

0
répondu 2 revscaddzooks 2009-07-10 03:49:28

Cette "approche fonctionnelle" abstraction des fuites de gros temps. Rien au niveau du langage n'empêche les effets secondaires. Aussi longtemps que vous pouvez le faire appeler votre lambda/délégué pour chaque élément dans le conteneur - vous obtiendrez le comportement "ForEach".

Voici par exemple une façon de fusionner srcDictionary en destDictionary (si la clé existe déjà - overwrites)

il s'agit d'un hack, et ne doit pas être utilisé dans un code de production.

var b = srcDictionary.Select(
                             x=>
                                {
                                  destDictionary[x.Key] = x.Value;
                                  return true;
                                }
                             ).Count();
0
répondu Zar Shardan 2012-07-03 00:47:40

pour VB.NET vous devez utiliser:

listVariable.ForEach(Sub(i) i.Property = "Value")
0
répondu Israel Margulies 2013-03-14 19:41:58

encore un autre ForEach exemple

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
répondu neil martin 2012-12-17 08:20:22

si vous faites cela par exemple parce que vous avez besoin de l'index dans votre itération, vous pouvez toujours utiliser une construction où:

linqObject.Where((obj, index) => {
  DoWork(obj, index);
  return true;
}).ToArray(); //MUST CALL ToArray() or ToList() or something to execute the lazy query, or the loop won't actually execute

ceci a l'avantage supplémentaire que le tableau original est retourné" inchangé " (les objets référencés par la liste sont les mêmes, bien qu'ils puissent ne pas avoir les mêmes données), ce qui est souvent désirable dans les méthodologies de programmation fonctionnelle / chaîne comme LINQ.

-2
répondu Walt W 2010-09-01 17:26:18