2010-02-19 6 views
4

Dire que j'ai une classe:Liste <T> .foreach par rapport à l'extension personnalisée IEnumerable <T>

public class MyClass 
{ 
    ... 
} 

et une méthode de webservice qui retourne un IEnumerable<MyClass>

Le consommateur du webservice définit une méthode:

Maintenant, le consommateur peut appeler le DoSomething sur le résultat de la méthode de service Web de deux façons:Maintenant, le consommateur peut appeler DoSomething sur le résultat de la méthode de service Web de deux manières:
var result = // web service call 

foreach(var myClass in result) 
{ 
    DoSomething(myClass); 
} 

ou:

var result = // web service call 

result.ToList().ForEach(DoSomething); 

Inutile de dire que je préfère de beaucoup la seconde manière, car il est beaucoup plus court et plus expressif (une fois que vous vous habituez à la syntaxe, que j'ai).

Maintenant, la méthode de service Web expose seulement une IEnumerable<MyClass>, mais il retourne en fait un List<MyClass> qui (AFAIK) signifie que l'objet sérialisé réelle est encore un List<T>. Cependant, j'ai trouvé (en utilisant le réflecteur) que la méthode Linq ToList() fait une copie de tous les objets dans le IEnumerable<T> quel que soit le type d'exécution réel (à mon avis, il pourrait avoir casté l'argument à un List<T> s'il était déjà un).

Cela a évidemment un certain surcoût de performance, en particulier pour les grandes listes (ou les listes d'objets volumineux).

Alors, que puis-je faire pour résoudre ce problème, et pourquoi n'y a-t-il pas de méthode ForEach dans Linq? Par ailleurs, sa question est vaguement liée à this one.

+0

"Pourquoi n'y a-t-il pas de méthode ForEach dans Linq?": Http://stackoverflow.com/questions/800151/why-is- foreach-on-ilistt-and-not-on-ienumerablet –

+0

'ToList()' ne fait pas de copie des objets sauf s'ils sont de type valeur. Au lieu de cela, il crée une liste avec les mêmes références, ce qui permet une modification de la liste d'origine lorsque la boucle est encore active (dans certains cas, vous pouvez le vouloir). Il permet également l'utilisation d'un délégué variable, dans le cas où l'action 'DoSomething' dépend d'autre chose ou est un argument, et c'est probablement le cas où' ForEach' est logique - bien que vous puissiez vous en passer. – RedGlyph

Répondre

4

J'utilise 2 méthodes. On itère la liste, on travaille avec eval paresseux. Je les utilise comme la situation définit.

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

    public static IEnumerable<T> ForEachImmediate<T>(this IEnumerable<T> source, Action<T> action) 
    { 
     foreach (var item in source) 
     { 
      action(item); 
     } 
     return source; 
    } 
2

Vous pouvez écrire votre propre méthode d'extension pour IEnumerable<T> comme ceci:

public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action) 
    { 
     foreach (T t in enumerable) 
      action(t); 
    } 

Une telle méthode n'est pas présent dans Linq, parce que Linq est essentiellement destiné aux requêtes, et non pour des itérations simples. Notez également que lors de l'utilisation d'une instance réelle List<T>, la méthode d'extension ne sera pas appelée car les méthodes d'instance ont la priorité sur les méthodes d'extension lorsqu'elles partagent la signature.

Le code suivant, par exemple, ne serait pas invoquer la méthode d'extension:

var l = new List<MyClass>(); 
l.Add(new MyClass()); 
l.ForEach(DoSomething); 

Alors que les suivantes seraient:

IEnumerable<MyClass> l = new List<MyClass>(new []{new MyClass()}); 
l.ForEach(DoSomething); 
+2

Honnêtement, je n'aime vraiment pas ça. Ce n'est pas faux, donc je ne vais pas le baisser, mais vous confondez la sémantique fonctionnelle (fermeture) et impérative ici et cela peut facilement conduire à des problèmes inattendus, difficiles à déboguer ou, au mieux, à une mauvaise lisibilité. – Aaronaught

+2

puis redescendez-le Aaronaught –

5

Vous pouvez écrire une méthode d'extension, mais il y a good reasons pourquoi ForEach est pas implémenté sur IEnumerable<T>. Le second exemple

result.ToList().ForEach(DoSomething); 

copie le IEnumerable dans une liste (sauf si elle est déjà une liste, je suppose) donc vous êtes mieux juste itérer IEnumerable avec un bon vieux foreach(var r in result) {}.

Addendum:

Pour moi, le point clé de l'article d'Eric Lippert est que l'ajout ForEach n'a aucun avantage et ajoute quelques pièges potentiels:

La deuxième raison est que faire si ajouts zéro nouvelle pouvoir de représentation à la langue. En faisant cela, vous pouvez réécrire ce code parfaitement clair:

foreach (Foo toto in foos) {déclaration impliquant foo; }

dans ce code:

foos.ForEach ((Foo foo) => {statement impliquant foo;});

qui utilise presque exactement les mêmes caractères dans l'ordre légèrement différent . Et pourtant la deuxième version est plus difficile à comprendre, plus difficile à déboguer, et introduit la sémantique de fermeture, ainsi potentiellement changer l'objet vies de manière subtile.

+1

+1 pour le lien vers le post définitif d'Eric Lippert sur pourquoi il n'y a pas de ForEach dans Linq –

+0

C'est correct, LINQ a été conçu pour l'interrogation, pas pour l'itération –

+0

Je suis d'accord avec l'analyse d'Eric de son exemple, mais Je pense qu'il néglige ceci: 'foos.ForEach (SomeMethod)'.Il n'y a pas de sémantique de clôture à proprement parler, et à mon avis c'est en fait plus facile à lire et plus concis qu'une boucle 'foreach' quand tout ce que vous voulez faire est d'appeler une méthode pour chaque élément d'une liste. En raison de l'avantage évident et de l'absence d'inconvénients significatifs, je suis allé de l'avant et mis en œuvre ma propre méthode d'extension, et je n'abandonnerai jamais! –

5

Je préférerais ceci: -

foreach (var item in result.ToList()) 
{ 
    DoSomething(item); 
} 

Son beaucoup plus clair idiome, il est dit recueillir une liste de choses ensemble puis faire quelque chose d'important qui peut changer l'état de l'application. Son ancienne école mais cela fonctionne et est en fait plus compréhensible à un public plus large.

+0

Et plus facile à déboguer, à mon humble avis. – TheSoftwareJedi

+0

Je suis d'accord avec foreach mais pourquoi convertir en Liste si c'est déjà IEnumerable? –

+0

@Jamie: Le 'ToList' est là parce que c'est dans la question originale. Il y a des moments où c'est nécessaire. Si par exemple 'DoSomething' représente une opération qui supprime des éléments d'une collection qui est la source ultime de' IEnumerable', vous obtiendrez probablement une exception si vous n'incluez pas 'ToList'. Bien sûr, si vous êtes heureux que 'DoSomething' ne va pas faire cela alors très probablement ce n'est pas nécessaire. – AnthonyWJones

-2

Si vous décidez de le faire via une méthode d'extension, je l'appellerais ForAll au lieu de ForEach. Ceci afin d'utiliser la même syntaxe que l'utilisation Parallel Extensions in .NET 4.0:

var result = // web service call 
result.AsParallel().ForAll(DoSomething); 
+0

Pourquoi l'appeler 'ForAll', introduisant une confusion avec la version parallélisée, alors qu'il existe déjà une méthode Framework 'ForEach' avec cette sémantique exacte (juste un type différent)? –

1

Vous pouvez écrire votre propre méthode d'extension ToList (cette liste theList) {return theList;} et éviter la surcharge. Puisque votre méthode d'extension est la plus spécifique, elle sera appelée, pas celle sur IEnumerable

+0

Bon point .... –

Questions connexes