Comment fonctionne l'instruction LINQ suivante?

160

Comment fonctionne l' instruction LINQ suivante ?

Voici mon code:

var list = new List<int>{1,2,4,5,6};
var even = list.Where(m => m%2 == 0);
list.Add(8);
foreach (var i in even)
{
    Console.WriteLine(i);
}

Production: 2, 4, 6, 8

Pourquoi pas 2, 4, 6?

Atish Dipongkor - MVP
la source
102
Le résultat d'une expression de requête est une requête, pas l'exécution de la requête.
Eric Lippert
6
Pour plus d'informations, consultez la réponse acceptée à cette question .
Daniel
9
Vous pouvez sûrement penser à un titre qui résume réellement la question.
Matt Ball
2
Je suppose que les votes négatifs (6 maintenant, pas les miens) sont qu'ils considèrent le titre de la question trop générique pour être une bonne question. Mais, vu le nombre de votes positifs et devenant la principale question de la semaine dans la newsletter, je ne pense pas que vous deviez vous en préoccuper trop.
Abel

Réponses:

235

La sortie est 2,4,6,8due à une exécution différée .

La requête est en fait exécutée lorsque la variable de requête est itérée, et non lorsque la variable de requête est créée. C'est ce qu'on appelle une exécution différée.

- Suprotim Agarwal, "Exécution différée ou immédiate des requêtes dans LINQ"

Il existe une autre exécution appelée Exécution immédiate des requêtes , qui est utile pour la mise en cache des résultats des requêtes. De Suprotim Agarwal à nouveau:

Pour forcer l'exécution immédiate d'une requête qui ne produit pas de valeur singleton, vous pouvez appeler la méthode ToList(), ToDictionary(), ToArray(), Count(), Average()ou Max()sur une requête ou une variable de requête. Ce sont des opérateurs de conversion qui vous permettent de faire une copie / un instantané du résultat et d'y accéder autant de fois que vous le souhaitez, sans qu'il soit nécessaire de réexécuter la requête.

Si vous voulez que la sortie soit 2,4,6, utilisez .ToList():

var list = new List<int>{1,2,4,5,6};
var even = list.Where(m => m%2 == 0).ToList();
list.Add(8);
foreach (var i in even)
 {
    Console.WriteLine(i);
 }
Atish Dipongkor - MVP
la source
8
Count (), Max (), Avg (), Sum () et probablement d'autres méthodes qui doivent prendre en compte toute la liste, provoquent également l'évaluation de la requête.
Kenned le
1
J'ai souvent pensé à avoir, disons, 'filteredList' comme variable, plutôt que 'filterList ()' comme méthode - l'idée étant que vous itérez dessus chaque fois que vous voulez que la liste soit filtrée, plutôt que d'appeler une méthode. Cela pourrait être une méthode intéressante, quoique inhabituelle et peut-être imparfaite en termes de performances.
Katana314
4
@Sebastian - Suite à @ commentaire de Kenned, .First(), .FirstOrDefault(), .Single()et .SingleOrDefault()déclencher également l'évaluation de la requête.
Scotty.NET
4
étonnant comment vous avez obtenu la réponse en moins de 30 secondes: D
MC
2
@MC Je ne sais pas pourquoi vous posez cette question. Une réponse complète n'a pas été donnée à la fois. Il a été édité plusieurs fois.
Atish Dipongkor - MVP
11

Cela s'est produit à cause d'une exécution différée, ce qui signifie que le calcul de l'expression n'est pas exécuté tant qu'il n'est pas nécessaire quelque part. Cela améliore les performances si les données sont trop volumineuses.

Sandeep Chauhan
la source
3
Vous pouvez nuancer cela, car cela peut également signifier que votre énumération coûteuse est exécutée plusieurs fois. Dans un tel cas, vous pourriez même subir une perte de performances.
Grimace of Despair
0

La raison en est l'exécution différée de votre expression lambda. La requête est exécutée lorsque vous démarrez l'itération dans la boucle foreach.

Prateek Dhuper
la source
11
Techniquement, c'est l'exécution différée de l' itérateur , pas le lambda .
D Stanley
0

Lorsque vous utilisez un IEnumerable <> obtenu à partir de LINQ, seule une classe Enumerator est créée et l'itération ne démarre que lorsque vous l'utilisez dans une certaine marche.

Miguel
la source
-1

Vous obtenez ce résultat en raison d'une exécution différée, ce qui signifie que le résultat n'est en fait pas évalué avant son premier accès.

Pour le rendre plus clair, ajoutez simplement 10 à la liste à la fin de votre snipet, puis imprimez à nouveau, vous n'obtiendrez pas 10 en sortie

     var list = new List<int>{1,2,4,5,6};
    var even = list.Where(m => m%2 == 0).Tolist();
    list.Add(8);
    foreach (var i in even)
    {
        Console.WriteLine(i);
    }
//new*
    list.Add(10);
    foreach (var i in even)
    {
        Console.WriteLine(i);
    }
sandeep
la source
Avez-vous réellement essayé cela? Je reçois 10dans la sortie.
Mark Hurd
bonne prise @MarkHurd oui n'a pas ajouté .ToList (). édité le message maintenant, il devrait donner le résultat attendu. Mon attente était que l'expression n'est évaluée que lorsque vous utilisez le var pour la première fois, mais il semble qu'elle soit évaluée à chaque fois
sandeep
Maintenant, il ne contiendra 8aucune sortie.
Mark Hurd