web-dev-qa-db-fra.com

Pourquoi il n'y a pas de méthode d'extension ForEach sur IEnumerable?

Inspiré par une autre question sur la fonction manquante Zip:

Pourquoi n'y a-t-il pas de méthode d'extension ForEach dans la classe Enumerable? Ou n'importe où? La seule classe qui obtient une méthode ForEach est List<>. Y a-t-il une raison pour laquelle il manque (performance)?

355
Cameron MacFarland

Il existe déjà une déclaration foreach incluse dans la langue qui fait le travail la plupart du temps.

Je détesterais voir ce qui suit:

list.ForEach( item =>
{
    item.DoSomething();
} );

Au lieu de:

foreach(Item item in list)
{
     item.DoSomething();
}

Ce dernier est plus clair et facile à lire dans la plupart des cas, bien qu’il soit peut-être un peu plus long à taper.

Cependant, je dois admettre que j’ai changé de position sur cette question; une méthode d'extension ForEach () serait en effet utile dans certaines situations.

Voici les principales différences entre l'énoncé et la méthode:

  • Vérification de type: foreach est fait à l'exécution, ForEach () est à la compilation (Big Plus!)
  • La syntaxe pour appeler un délégué est en effet beaucoup plus simple: objects.ForEach (DoSomething);
  • ForEach () pourrait être enchaîné: bien que la méchanceté/utilité d’une telle fonctionnalité soit ouverte à la discussion.

Ce sont tous d'excellents points soulevés par de nombreuses personnes ici et je peux comprendre pourquoi les gens manquent à la fonction. Je n’aimerais pas que Microsoft ajoute une méthode ForEach standard dans la prochaine itération du framework.

187
Coincoin

La méthode ForEach a été ajoutée avant LINQ. Si vous ajoutez l'extension ForEach, elle ne sera jamais appelée pour les instances de liste à cause des contraintes des méthodes d'extension. Je pense que la raison pour laquelle il n'a pas été ajouté est de ne pas interférer avec celui existant.

Cependant, si vous manquez vraiment cette petite fonction de Nice, vous pouvez déployer votre propre version

public static void ForEach<T>(
    this IEnumerable<T> source,
    Action<T> action)
{
    foreach (T element in source) 
        action(element);
}
72
aku

Vous pouvez écrire cette méthode d'extension:

// Possibly call this "Do"
IEnumerable<T> Apply<T> (this IEnumerable<T> source, Action<T> action)
{
    foreach (var e in source)
    {
        action(e);
        yield return e;
    }
}

Pros

Permet de chaîner:

MySequence
    .Apply(...)
    .Apply(...)
    .Apply(...);

inconvénients

En réalité, il ne fera rien tant que vous ne ferez rien pour forcer l'itération. Pour cette raison, il ne devrait pas s'appeler .ForEach(). Vous pouvez écrire .ToList() à la fin ou cette méthode d'extension également:

// possibly call this "Realize"
IEnumerable<T> Done<T> (this IEnumerable<T> source)
{
    foreach (var e in source)
    {
        // do nothing
        ;
    }

    return source;
}

Cela peut constituer un écart trop important par rapport aux bibliothèques C # d’expédition; Les lecteurs qui ne connaissent pas vos méthodes d'extension ne sauront pas comment interpréter votre code.

50
Jay Bazuzi

La discussion ici donne la réponse:

En fait, la discussion spécifique à laquelle j'ai assisté était en fait liée à la pureté fonctionnelle. Dans une expression, il y a souvent des hypothèses sur le fait de ne pas avoir d'effets secondaires. Avoir ForEach, c'est spécifiquement inviter des effets secondaires plutôt que de simplement les supporter. - Keith Farmer (partenaire)

En gros, il a été décidé de garder les méthodes d'extension fonctionnellement "pures". Un ForEach encouragerait les effets secondaires lors de l'utilisation des méthodes d'extension Enumerable, ce qui n'était pas l'intention.

33
mancaus

Bien que je convienne qu'il est préférable d'utiliser la construction foreach intégrée dans la plupart des cas, l'utilisation de cette variante sur l'extension ForEach <> est un peu plus agréable que de devoir gérer l'index dans un foreach normal:

public static int ForEach<T>(this IEnumerable<T> list, Action<int, T> action)
{
    if (action == null) throw new ArgumentNullException("action");

    var index = 0;

    foreach (var elem in list)
        action(index++, elem);

    return index;
}
var people = new[] { "Moe", "Curly", "Larry" };
people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p));

Vous donnerait:

Person #0 is Moe
Person #1 is Curly
Person #2 is Larry
16
Chris Zwiryk

Une solution consiste à écrire .ToList().ForEach(x => ...).

pros

Facile à comprendre - le lecteur n'a besoin que de savoir ce qui est livré avec C #, pas de méthodes d'extension supplémentaires.

Le bruit syntaxique est très doux (ajoute seulement un peu de code extra).

Ne coûte généralement pas plus de mémoire, puisqu'un .ForEach() natif devrait de toute façon réaliser l'ensemble de la collection.

contre

L'ordre des opérations n'est pas idéal. Je préférerais réaliser un élément, puis agir, puis répéter. Ce code réalise tous les éléments en premier, puis les agit en séquence.

Si vous réalisez une exception avec la liste, vous ne pourrez jamais agir sur un seul élément.

Si l'énumération est infinie (comme les nombres naturels), vous n'avez pas de chance.

14
Jay Bazuzi

Je me suis toujours demandé cela, c'est pourquoi je porte toujours ceci avec moi:

public static void ForEach<T>(this IEnumerable<T> col, Action<T> action)
{
    if (action == null)
    {
        throw new ArgumentNullException("action");
    }
    foreach (var item in col)
    {
        action(item);
    }
}

Belle petite méthode d'extension.

13
Aaron Powell

Il y a donc eu beaucoup de commentaires sur le fait qu'une méthode d'extension ForEach n'est pas appropriée car elle ne renvoie pas de valeur comme les méthodes d'extension LINQ. Bien qu'il s'agisse d'une déclaration factuelle, ce n'est pas tout à fait vrai.

Les méthodes d'extension LINQ renvoient toutes une valeur pour pouvoir être chaînées:

collection.Where(i => i.Name = "hello").Select(i => i.FullName);

Cependant, le fait que LINQ soit implémenté à l'aide de méthodes d'extension ne signifie pas que les méthodes d'extension doivent doivent être utilisées de la même manière et renvoyer une valeur. L'écriture d'une méthode d'extension pour exposer une fonctionnalité commune qui ne renvoie pas de valeur est une utilisation parfaitement valide.

L'argument spécifique à propos de ForEach est que, en fonction des contraintes sur les méthodes d'extension (à savoir qu'une méthode d'extension jamais écrasera une méthode héritée avec le même signature), il peut y avoir une situation dans laquelle la méthode d'extension personnalisée est disponible sur toutes les classes qui implémentent IEnumerable<T> sauf List<T>. Cela peut être source de confusion lorsque les méthodes commencent à se comporter différemment selon que la méthode d'extension ou la méthode héritée est appelée ou non.

8
Scott Dorman

Vous pouvez utiliser le Select (chaînable, mais évalué paresseusement), tout d’abord en effectuant votre opération, puis en retournant l’identité (ou autre chose si vous préférez)

IEnumerable<string> people = new List<string>(){"alica", "bob", "john", "pete"};
people.Select(p => { Console.WriteLine(p); return p; });

Vous devrez vous assurer qu'il est toujours évalué, soit avec Count() (l'opération la moins chère à énumérer autant que possible) ou une autre opération dont vous aviez besoin de toute façon.

J'aimerais bien que cela soit introduit dans la bibliothèque standard:

static IEnumerable<T> WithLazySideEffect(this IEnumerable<T> src, Action<T> action) {
  return src.Select(i => { action(i); return i; } );
}

Le code ci-dessus devient alors people.WithLazySideEffect(p => Console.WriteLine(p)), ce qui est effectivement équivalent à foreach, mais paresseux et chaînable.

7
Martijn

@Coincoin

La puissance réelle de la méthode d’extension foreach implique la possibilité de réutiliser Action<> sans ajouter de méthodes inutiles à votre code. Supposons que vous avez 10 listes et que vous souhaitez y appliquer la même logique, et qu'une fonction correspondante ne rentre pas dans votre classe et n'est pas réutilisée. Au lieu d’avoir dix boucles for, ou une fonction générique qui est évidemment une aide qui n’appartient pas, vous pouvez conserver toute votre logique au même endroit (le Action<>. Ainsi, des dizaines de lignes sont remplacées par

Action<blah,blah> f = { foo };

List1.ForEach(p => f(p))
List2.ForEach(p => f(p))

etc...

La logique est au même endroit et vous n'avez pas pollué votre classe.

4
diehard2

Notez que le MoreLINQ NuGet fournit la méthode d'extension ForEach que vous recherchez (ainsi qu'une méthode Pipe qui s'exécute le délégué et donne son résultat). Voir:

4
Dave Clausen

J'ai écrit un billet de blog à ce sujet: http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx

Vous pouvez voter ici si vous souhaitez voir cette méthode dans .NET 4.0: http://connect.Microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=27909

2
Kirill Osenkov

Je voudrais développer réponse d'Ak .

Si vous voulez appeler une méthode dans le seul but de produire un effet secondaire sans itérer tout l'énumérable, vous pouvez utiliser ceci:

private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) {
    foreach (var x in xs) {
        f(x); yield return x;
    }
}
2
fredefox

Dans la version 3.5, toutes les méthodes d'extension ajoutées à IEnumerable existent pour la prise en charge de LINQ (notez qu'elles sont définies dans la classe System.Linq.Enumerable). Dans cet article, j’explique pourquoi foreach n’appartient pas à LINQ: Méthode d’extension LINQ existante similaire à Parallel.For?

2
Neil

Est-ce moi ou la liste <T> .Foreach a été rendue obsolète par Linq. À l'origine il y avait

foreach(X x in Y) 

où Y devait simplement être IEnumerable (Pre 2.0) et implémenter GetEnumerator (). Si vous regardez le MSIL généré, vous pouvez voir qu'il est exactement le même que

IEnumerator<int> enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
    int i = enumerator.Current;

    Console.WriteLine(i);
}

(Voir http://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspx pour le MSIL)

Ensuite, dans DotNet2.0, les génériques et la liste sont apparus. Foreach m'a toujours semblé être une implémentation du modèle Vistor (voir Modèles de conception de Gamma, Helm, Johnson, Vlissides).

Maintenant bien sûr dans la version 3.5, nous pouvons utiliser un Lambda dans le même sens, par exemple, essayez http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and- linq-oh-my /

2
user18784

La plupart des méthodes d'extension LINQ renvoient des résultats. ForEach ne correspond pas à ce modèle car il ne renvoie rien.

2
leppie

C'est en partie parce que les concepteurs de langage ne sont pas d'accord avec cela d'un point de vue philosophique.

  • Ne pas avoir (et tester ...) une fonctionnalité représente moins de travail que d'avoir une fonctionnalité.
  • Ce n'est pas vraiment plus court (dans certains cas, ce n'est pas le cas, mais ce ne serait pas l'utilisation principale).
  • Son but est d’avoir des effets secondaires, ce qui n’est pas le propos de linq.
  • Pourquoi avoir une autre façon de faire la même chose qu'une fonctionnalité que nous avons déjà? (foreach mot-clé)

https://blogs.msdn.Microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/

2
McKay

Si vous avez F # (qui sera dans la prochaine version de .NET), vous pouvez utiliser

Seq.iter doSomething myIEnumerable

2
TraumaPony

Vous pouvez utiliser select quand vous voulez retourner quelque chose. Si vous ne le faites pas, vous pouvez utiliser d'abord ToList, car vous ne souhaiterez probablement rien modifier dans la collection.

2
Paco

Pour utiliser Foreach, votre liste doit être chargée dans la mémoire primaire. Mais en raison de la nature de chargement paresseux de IEnumerable, ils ne fournissent pas ForEach dans IEnumerable.

Cependant, en chargeant avec impatience (en utilisant ToList ()), vous pouvez charger votre liste en mémoire et tirer parti de ForEach.

1
Arjun Bhalodiya

Personne n'a encore indiqué que ForEach <T> entraînait une vérification du type de temps de compilation où le mot clé foreach était vérifié à l'exécution.

Ayant effectué quelques refactorisations où les deux méthodes étaient utilisées dans le code, je préfère .ForEach, car je devais traquer les échecs de test/les échecs d'exécution pour trouver les problèmes foreach.

0
Squirrel