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)?
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:
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.
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);
}
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.
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.
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
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.
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.
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.
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.
@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.
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:
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
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;
}
}
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?
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 /
La plupart des méthodes d'extension LINQ renvoient des résultats. ForEach ne correspond pas à ce modèle car il ne renvoie rien.
C'est en partie parce que les concepteurs de langage ne sont pas d'accord avec cela d'un point de vue philosophique.
https://blogs.msdn.Microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/
Si vous avez F # (qui sera dans la prochaine version de .NET), vous pouvez utiliser
Seq.iter doSomething myIEnumerable
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.
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.
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.