Question

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 syntaxe réelle?

Était-ce utile?

La solution

Il n'y a pas d'extension ForEach pour IEnumerable ; uniquement pour Liste < T > . Alors 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);
    }
}

Autres conseils

Fredrik a fourni le correctif, mais il serait peut-être utile de se demander pourquoi ce n’est pas dans le cadre pour commencer. Je pense 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 purement basée sur des effets secondaires.

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

Mise à jour 7/17/2012: apparemment à partir de la version 5.0, le comportement de à chaque décrit ci-dessous a été modifié et " 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 # & # 8805; 5.0.

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

Le problème avec "foreach" " en C # antérieure à la version 5.0 , c'est qu'il est incompatible avec la façon dont l'équivalent "compréhension" " fonctionne dans d'autres langues, et avec la manière dont je m'attendrais à ce qu'il fonctionne (l'opinion personnelle est exprimée ici uniquement parce que d'autres personnes ont mentionné leur opinion sur la lisibilité) Consultez toutes les questions relatives à & Accès à la fermeture modifiée " ainsi que & amp; La fermeture sur la variable de boucle considérée comme nuisible . C’est seulement "nocif" à cause de la manière " foreach " est implémenté en C #.

Prenez les exemples suivants en utilisant la méthode d'extension fonctionnellement équivalente à celle utilisée dans 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 tout à fait exagéré 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", vous obtiendrez des résultats très étranges et potentiellement non déterministes.

Le test suivant utilisant " ForEach " la méthode d'extension 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()));
}

Vous pouvez utiliser l'extension FirstOrDefault () , disponible pour IEnumerable < T > . En renvoyant false à partir 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; });

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

De cette manière, 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 tout autre moyen de forcer la méthode à fonctionner de manière complète. énumérable. 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 vous 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 ToList () , le test échouera, car StringBuilder contient une chaîne vide. En effet, aucune méthode n’a forcé ForEach à énumérer.

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é, les méthodes ici et à l'étranger LINQ et IEnumerable devraient être sans effets secondaires.

Voulez-vous vraiment "faire quelque chose"? à chaque élément dans le 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, 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) 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 un nouveau 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.

Si vous voulez agir en tant que listes 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;
        }
    }
}

Microsoft a publié une version expérimentale des extensions interactives de LINQ (également sur NuGet , voir Profil de RxTeams pour plus de liens). La 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 plus lisible. Décompressez l’archive docs et recherchez 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));

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

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

faire une boucle foreach parallèle sur un IEnumerable.

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));

Ceci ne sera exécuté que lorsque vous aurez effectué 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 programmer autour d’eux afin de sauver quelques caractères avant l’évaluation paresseuse.

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).

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 tel dans la BCL, il existe toujours une option dans l'espace de noms System , si vous avez déjà une référence à < a href = "https://www.nuget.org/packages/Rx-Main" rel = "nofollow noreferrer"> Extension réactive (et si vous ne le faites pas, vous devriez l'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.

Nous avons maintenant la possibilité 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 vermifuges.

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

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

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

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

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

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

ForEach peut également être Enchaîné , il suffit de revenir à la ligne de conduite après l'action. rester à l'aise

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

Une version impatiente de la mise en œuvre.

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
}

Modifier: une version Lazy utilise le rendement, comme this .

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

La version lazy NEED doit être matérialisée, ToList () par exemple, sinon rien ne se produit. Voir ci-dessous d'excellents commentaires de ToolmakerSteve.

IQueryable<Product> query = Products.Where(...);
query.ForEachLazy(t => t.Price = t.Price + 1.00)
    .ToList(); //without this line, below SubmitChanges() does nothing.
SubmitChanges();

Je garde ForEach () et ForEachLazy () dans ma bibliothèque.

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 (et pas seulement parce qu'elles ne le sont pas, tout délégué peut effectuer des effets secondaires).

Considérez 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 action et la traduire dans la méthode correspondante.

Pour VB.NET, vous devez utiliser:

listVariable.ForEach(Sub(i) i.Property = "Value")

Pour rester fluide, on peut utiliser une telle astuce:

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

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

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

https://github.com/morelinq/MoreLINQ

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;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top