web-dev-qa-db-fra.com

LINQ + Foreach vs Foreach + If

J'ai besoin d'itérer sur une liste d'objets, en faisant quelque chose uniquement pour les objets dont la propriété booléenne est définie sur true. Je discute entre ce code

foreach (RouteParameter parameter in parameters.Where(p => p.Condition))
{ //do something }

et ce code

foreach (RouteParameter parameter in parameters)
{ 
  if !parameter.Condition
    continue;
  //do something
}

Le premier code est évidemment plus propre, mais je soupçonne qu'il va parcourir la liste deux fois - une fois pour la requête et une fois pour foreach. Ce ne sera pas une énorme liste, donc je ne suis pas trop préoccupé par les performances, mais l'idée de boucler deux fois juste bugs moi.

Question: Existe-t-il un moyen propre/joli d'écrire ceci sans boucler deux fois?

54
Joel

Jon Skeet fait parfois une démo LINQ en direct pour expliquer comment cela fonctionne. Imaginez que vous ayez trois personnes sur scène. Sur la gauche, nous avons un gars qui a un jeu de cartes mélangé. Au milieu, nous avons un gars qui ne fait que passer des cartons rouges, et à droite, nous avons un gars qui veut des cartes.

Le gars à droite pousse le gars au milieu. Le gars du milieu pousse le gars de gauche. Le gars à gauche donne au gars au milieu une carte. S'il est noir, le gars du milieu le jette sur le sol et le pousse à nouveau jusqu'à ce qu'il reçoive un carton rouge, qu'il tend ensuite au gars de droite. Ensuite, le gars de droite pousse à nouveau le gars au milieu.

Cela continue jusqu'à ce que le gars de gauche soit à court de cartes.

Le jeu n'a pas été parcouru du début à la fin plus d'une fois. Cependant, le gars de gauche et le gars du milieu ont traité 52 cartes, et le gars de droite a traité 26 cartes. Il y avait un total de 52 + 52 + 26 opérations sur les cartes, mais le jeu n'a été bouclé qu'une seule fois .

Votre version "LINQ" et la version "continue" sont la même chose; si tu avais

foreach(var card in deck)
{
    if (card.IsBlack) continue;
    ... use card ...

puis il y a 52 opérations qui récupèrent chaque carte du jeu, 52 opérations qui testent pour voir si chaque carte est noire et 26 opérations qui agissent sur la carte rouge. C'est exactement la même chose.

127
Eric Lippert

La plupart des opérateurs Linq tels que Where sont implémentés pour prendre en charge l'exécution différée et paresseuse . Dans votre exemple, la liste sera répétée une seule fois car l'énumérateur assis derrière l'IEnumerable renvoyé par Where énumérera la liste jusqu'à ce qu'il trouve un élément correspondant au prédicat, le produise et ne continuera que lorsque cela lui sera demandé pour l'élément suivant.

Du point de vue du code, je préférerais que la variante utilise où, bien que l'on puisse affirmer que vous pouvez déclarer un local pour parameters.Where(p => p.Condition).

La série Edulinq de Jon Skeet est fortement recommandée, la lecture de quelques morceaux devrait vous aider à comprendre les opérateurs LINQ.

35
Johannes Rudolph

En fait, ce n'est pas "boucler deux fois". La clause .Where Utilise une exécution différée. En d'autres termes, pratiquement aucun travail n'est effectué lorsque vous appelez .Where, Mais lorsque vous parcourez le résultat, il parcourra la liste d'origine et ne passera que par les éléments qui correspondent à votre condition. Si vous y pensez en termes d'exécution du code, vous effectuez effectivement ceci:

Func<Parameter, bool> matchesCondition = p => p.Condition;
foreach(var parameter in parameters)
{
    if(matchesCondition(parameter))
    {
        ...
    }
}

Pour une question de style, je préfère personnellement quelque chose de plus comme:

var matchingParameters = parameters.Where(p => p.Condition);
foreach(var parameter in matchingParameters)
{
}
26
StriplingWarrior