J’ai des doutes sur le fonctionnement des énumérateurs et LINQ. Considérez ces deux sélections simples:
List sel = (from animal in Animals join race in Species on animal.SpeciesKey equals race.SpeciesKey select animal).Distinct().ToList();
ou
IEnumerable sel = (from animal in Animals join race in Species on animal.SpeciesKey equals race.SpeciesKey select animal).Distinct();
J’ai changé les noms de mes objects originaux pour que cela ressemble à un exemple plus générique. La requête elle-même n’est pas si importante. Ce que je veux demander, c’est ceci:
foreach (Animal animal in sel) { /*do stuff*/ }
J’ai remarqué que si j’utilise IEnumerable
, quand je débogue et inspecte “sel”, qui est dans ce cas le IEnumerable, il a des membres intéressants: “inner”, “outer”, “innerKeySelector” et “outerKeySelector”, ces 2 derniers semblent être des delegates. Le membre “interne” n’a pas d’instance “Animal”, mais plutôt des instances “Espèce”, ce qui était très étrange pour moi. Le membre “externe” contient des instances “Animal”. Je présume que les deux delegates déterminent ce qui entre et ce qui en sort?
J’ai remarqué que si j’utilise “Distinct”, le “inner” contient 6 éléments (ceci est incorrect car seulement 2 sont Distinct), mais le “external” contient les valeurs correctes. Encore une fois, les méthodes déléguées déterminent probablement cela, mais c’est un peu plus que ce que je sais sur IEnumerable.
Plus important encore, laquelle des deux options est la plus performante?
La conversion de la liste des méchants via .ToList()
?
Ou peut-être utiliser l’énumérateur directement?
Si vous le pouvez, veuillez également expliquer un peu ou lancer des liens expliquant cette utilisation de IEnumerable.
IEnumerable
décrit le comportement, tandis que List est une implémentation de ce comportement. Lorsque vous utilisez IEnumerable
, vous donnez au compilateur la possibilité de reporter le travail à plus tard, en optimisant éventuellement le processus. Si vous utilisez ToList (), vous forcez le compilateur à réifier les résultats immédiatement.
Lorsque j’emstack des expressions LINQ, j’utilise IEnumerable
, car en spécifiant uniquement le comportement, je donne à LINQ la possibilité de différer l’évaluation et éventuellement d’optimiser le programme. Rappelez-vous comment LINQ ne génère pas le SQL pour interroger la firebase database tant que vous ne l’énumérez pas? Considère ceci:
public IEnumerable AllSpotted() { return from a in Zoo.Animals where a.coat.HasSpots == true select a; } public IEnumerable Feline(IEnumerable sample) { return from a in sample where a.race.Family == "Felidae" select a; } public IEnumerable Canine(IEnumerable sample) { return from a in sample where a.race.Family == "Canidae" select a; }
Maintenant, vous avez une méthode qui sélectionne un échantillon initial (“AllSpotted”), ainsi que des filtres. Alors maintenant vous pouvez le faire:
var Leopards = Feline(AllSpotted()); var Hyenas = Canine(AllSpotted());
Alors, est-il plus rapide d’utiliser List over IEnumerable
? Seulement si vous souhaitez empêcher l’exécution d’une requête plus d’une fois. Mais est-ce mieux dans l’ensemble? Dans ce qui précède, les léopards et les hyènes sont convertis en requêtes SQL uniques , et la firebase database ne renvoie que les lignes pertinentes. Mais si nous avions renvoyé une liste à partir de AllSpotted()
, celle-ci pourrait être plus lente car la firebase database pourrait renvoyer beaucoup plus de données que nécessaire et nous perdons des cycles en filtrant le client.
Dans un programme, il peut être préférable de reporter la conversion de votre requête à une liste jusqu’à la fin, donc si je veux énumérer plusieurs fois les Léopards et les Hyènes, je le ferais:
List Leopards = Feline(AllSpotted()).ToList(); List Hyenas = Canine(AllSpotted()).ToList();
Une classe qui implémente IEnumerable
vous permet d’utiliser la syntaxe foreach
.
Fondamentalement, il a une méthode pour obtenir l’élément suivant dans la collection. Il n’a pas besoin que toute la collection soit en mémoire et ne sache pas combien d’éléments y sont foreach
, foreach
continue à recevoir l’élément suivant jusqu’à ce qu’il soit épuisé.
Cela peut être très utile dans certaines circonstances, par exemple dans une table de firebase database massive, vous ne souhaitez pas copier la totalité de la mémoire avant de commencer à traiter les lignes.
Now List
implémente IEnumerable
, mais représente la collection entière en mémoire. Si vous avez un IEnumerable
et que vous appelez .ToList()
vous créez une nouvelle liste avec le contenu de l’énumération en mémoire.
Votre expression linq renvoie une énumération et, par défaut, l’expression s’exécute lorsque vous effectuez une itération en utilisant foreach
. Une instruction IEnumerable
linq s’exécute lorsque vous effectuez une itération de foreach
, mais vous pouvez forcer son itération plus tôt en utilisant .ToList()
.
Voici ce que je veux dire:
var things = from item in BigDatabaseCall() where .... select item; // this will iterate through the entire linq statement: int count = things.Count(); // this will stop after iterating the first one, but will execute the linq again bool hasAnyRecs = things.Any(); // this will execute the linq statement *again* foreach( var thing in things ) ... // this will copy the results to a list in memory var list = things.ToList() // this won't iterate through again, the list knows how many items are in it int count2 = list.Count(); // this won't execute the linq statement - we have it copied to the list foreach( var thing in list ) ...
Il y a un très bon article écrit par: TechBlog de Claudio Bernasconi ici: Quand utiliser IEnumerable, ICollection, IList et List
Voici quelques points de base sur les scénarios et les fonctions:
La chose la plus importante à réaliser est que, en utilisant Linq, la requête n’est pas évaluée immédiatement. Il n’est exécuté que dans le cadre d’une itération à travers le IEnumerable
résultant dans un foreach
– c’est ce que font tous les delegates bizarres.
Ainsi, le premier exemple évalue la requête immédiatement en appelant ToList
et en mettant les résultats de la requête dans une liste.
Le deuxième exemple renvoie un IEnumerable
qui contient toutes les informations nécessaires pour exécuter la requête ultérieurement.
En termes de performance, la réponse est que cela dépend . Si vous souhaitez que les résultats soient évalués en même temps (par exemple, vous modifiez les structures que vous interrogez ultérieurement ou si vous ne souhaitez pas que l’itération sur IEnumerable
prenne beaucoup de temps), utilisez une liste. . Sinon, utilisez un IEnumerable
. La valeur par défaut devrait être d’utiliser l’évaluation à la demande dans le deuxième exemple, car cela utilise généralement moins de mémoire, à moins qu’il y ait une raison spécifique pour stocker les résultats dans une liste.
Personne n’a mentionné une différence cruciale, ironiquement répondu à une question fermée en double.
IEnumerable est en lecture seule et List ne l’est pas.
Voir Différence pratique entre List et IEnumerable
L’avantage de IEnumerable est l’exécution différée (généralement avec les bases de données). La requête ne sera pas exécutée tant que vous ne parcourez pas les données. C’est une requête en attente jusqu’à ce qu’elle soit nécessaire (aussi appelée chargement paresseux).
Si vous appelez ToList, la requête sera exécutée ou “matérialisée” comme je voudrais le dire.
Il y a des avantages et des inconvénients aux deux. Si vous appelez ToList, vous pouvez supprimer certains mystères quant à l’exécution de la requête. Si vous vous en tenez à IEnumerable, vous obtenez l’avantage que le programme ne fait aucun travail tant qu’il n’est pas réellement requirejs.
Si tout ce que vous voulez faire, c’est les énumérer, utilisez le IEnumerable
.
Attention, la modification de la collection d’origine énumérée est une opération dangereuse – dans ce cas, vous ToList
abord vous ToList
à la liste des ToList
. Cela créera un nouvel élément de liste pour chaque élément en mémoire, énumérant le IEnumerable
et est donc moins performant si vous ne l’ List
qu’une fois – mais plus sûr et parfois les méthodes List
sont pratiques (par exemple en access aléatoire).
Je partagerai un concept mal utilisé que je suis tombé dans un jour:
var names = new List {"mercedes", "mazda", "bmw", "fiat", "ferrari"}; var startingWith_M = names.Where(x => x.StartsWith("m")); var startingWith_F = names.Where(x => x.StartsWith("f")); // updating existing list names[0] = "ford"; // Guess what should be printed before continuing print( startingWith_M.ToList() ); print( startingWith_F.ToList() );
// I was expecting print( startingWith_M.ToList() ); // mercedes, mazda print( startingWith_F.ToList() ); // fiat, ferrari
// what printed actualy print( startingWith_M.ToList() ); // mazda print( startingWith_F.ToList() ); // ford, fiat, ferrari
Selon d’autres réponses, l’évaluation du résultat a été différée jusqu’à l’appel de la ToList
ou de méthodes d’appel similaires, par exemple ToArray
.
Je peux donc réécrire le code dans ce cas comme suit:
var names = new List {"mercedes", "mazda", "bmw", "fiat", "ferrari"}; // updating existing list names[0] = "ford"; // before calling ToList directly var startingWith_M = names.Where(x => x.StartsWith("m")); var startingWith_F = names.Where(x => x.StartsWith("f")); print( startingWith_M.ToList() ); print( startingWith_F.ToList() );
En plus de toutes les réponses affichées ci-dessus, voici mes deux cents. Il existe de nombreux autres types, autres que List, qui implémentent tels ICollection, ArrayList, etc. Ainsi, si IEnumerable est le paramètre de toute méthode, nous pouvons transmettre tous les types de collection à la fonction. C’est-à-dire que nous pouvons avoir une méthode pour opérer sur abstraction sans implémentation spécifique.