Si j'écris quelque chose comme ça:
var things = mythings
.Where(x => x.IsSomeValue)
.Where(y => y.IsSomeOtherValue)
Est-ce la même chose que:
var results1 = new List<Thing>();
foreach(var t in mythings)
if(t.IsSomeValue)
results1.Add(t);
var results2 = new List<Thing>();
foreach(var t in results1)
if(t.IsSomeOtherValue)
results2.Add(t);
Ou y a-t-il de la magie sous les couvertures qui fonctionne plus comme ceci:
var results = new List<Thing>();
foreach(var t in mythings)
if(t.IsSomeValue && t.IsSomeOtherValue)
results.Add(t);
Ou est-ce quelque chose de complètement différent?
Réponses:
Les requêtes LINQ sont paresseuses . Cela signifie que le code:
fait très peu. L'
mythings
énumérateur original ( ) n'est énuméré que lorsque l'énumérateur résultant (things
) est consommé, par exemple par uneforeach
boucle,,.ToList()
ou.ToArray()
.Si vous appelez
things.ToList()
, il est à peu près équivalent à votre dernier code, avec peut-être une surcharge (généralement insignifiante) des énumérateurs.De même, si vous utilisez une boucle foreach:
Ses performances sont similaires à:
Certains des avantages de performance de l'approche de la paresse pour les énumérables (par opposition au calcul de tous les résultats et à leur stockage dans une liste) sont qu'elle utilise très peu de mémoire (car un seul résultat est stocké à la fois) et qu'il n'y a pas de hausse significative -coût initial.
Si l'énumérable n'est que partiellement énuméré, cela est particulièrement important. Considérez ce code:
La façon dont LINQ est implémenté
mythings
ne sera énumérée que jusqu'au premier élément qui correspond à vos conditions where. Si cet élément est au début de la liste, cela peut être un énorme gain de performances (par exemple O (1) au lieu de O (n)).la source
foreach
est que LINQ utilise des appels de délégué, qui ont une surcharge. Cela peut être important lorsque les conditions s'exécutent très rapidement (ce qu'elles font souvent).ToList
ouToArray
. Si une telle chose avait été correctement intégréeIEnumerable
, il aurait été possible de demander à une liste de "prendre un instantané" tous les aspects qui pourraient changer à l'avenir sans avoir à tout générer.Le code suivant:
Est équivalent à rien, à cause de l'évaluation paresseuse, rien ne se passera.
Est différent, car l'évaluation sera lancée.
Chaque article
mythings
sera remis au premierWhere
. S'il passe, il sera remis au secondWhere
. S'il passe, il fera partie de la sortie.Donc, cela ressemble plus à ceci:
la source
Exécution différée mise à part (ce que les autres réponses expliquent déjà, je vais juste souligner un autre détail), c'est plus comme dans votre deuxième exemple.
Imaginons que vous appelez
ToList
lethings
.La mise en œuvre des
Enumerable.Where
retours aEnumerable.WhereListIterator
. Lorsque vous appelezWhere
celaWhereListIterator
(alias chaînageWhere
-appels), vous n'appelez plusEnumerable.Where
, maisEnumerable.WhereListIterator.Where
, qui combine en fait les prédicats (à l'aideEnumerable.CombinePredicates
).C'est plus comme ça
if(t.IsSomeValue && t.IsSomeOtherValue)
.la source
Non, ce n'est pas pareil. Dans votre exemple ,
things
unIEnumerable
, qui à ce stade n'est encore qu'un itérateur, pas un tableau ou une liste réelle. De plus puisqu'ellethings
n'est pas utilisée, la boucle n'est même jamais évaluée. Le typeIEnumerable
permet d'itérer à travers des élémentsyield
-ed par des instructions Linq et de les traiter plus loin avec plus d'instructions, ce qui signifie qu'au final vous n'avez vraiment qu'une seule boucle.Mais dès que vous ajoutez une instruction comme
.ToArray()
ou.ToList()
, vous ordonnez la création d'une structure de données réelle, ce qui met des limites à votre chaîne.Voir cette question SO connexe: /programming/2789389/how-do-i-implement-ienumerable
la source