web-dev-qa-db-fra.com

IEnumerable vs List - Quoi utiliser? Comment travaillent-ils?

J'ai quelques doutes sur le fonctionnement des énumérateurs et de LINQ. Considérez ces deux sélections simples:

List<Animal> sel = (from animal in Animals 
                    join race in Species
                    on animal.SpeciesKey equals race.SpeciesKey
                    select animal).Distinct().ToList();

ou

IEnumerable<Animal> 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 objets d'origine afin 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 est ceci:

foreach (Animal animal in sel) { /*do stuff*/ }
  1. J'ai remarqué que si j'utilise IEnumerable, lorsque je débogue et inspecte "sel", qui est dans ce cas le IEnumerable, il comporte des membres intéressants: "inner", "outer", "innerKeySelector" et "outerKeySelector" , ces 2 derniers semblent être des délégués. Le membre "interne" ne contient pas d'instance "Animal", mais "Species", ce qui était très étrange pour moi. Le membre "externe" contient des instances "Animal". Je présume que les deux délégués déterminent ce qui entre et ce qui en sort.

  2. J'ai remarqué que si j'utilise "Distinct", "interne" contient 6 éléments (ceci est incorrect car seuls 2 sont distincts), mais "externe" contient les valeurs correctes. Encore une fois, les méthodes déléguées le déterminent probablement, mais il s’agit d’un peu plus que ce que je sais de IEnumerable.

  3. Plus important encore, laquelle des deux options est la meilleure en termes de performances?

La conversion de liste diabolique via .ToList()?

Ou peut-être en utilisant l'énumérateur directement?

Si vous le pouvez, expliquez-nous également un peu ou jetez quelques liens expliquant cette utilisation de IEnumerable.

616
Axonn

IEnumerable décrit le comportement, alors que List est une implémentation de ce comportement. Lorsque vous utilisez IEnumerable, vous donnez au compilateur une chance de reporter le travail à plus tard, éventuellement en cours d’optimisation. Si vous utilisez ToList (), vous forcez le compilateur à réifier immédiatement les résultats.

Chaque fois que je "empile" des expressions LINQ, j'utilise IEnumerable, car en spécifiant uniquement le comportement, je donne à LINQ l'occasion de différer l'évaluation et éventuellement d'optimiser le programme. Rappelez-vous que LINQ ne génère pas le SQL pour interroger la base de données avant de l'énumérer. Considère ceci:

public IEnumerable<Animals> AllSpotted()
{
    return from a in Zoo.Animals
           where a.coat.HasSpots == true
           select a;
}

public IEnumerable<Animals> Feline(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Felidae"
           select a;
}

public IEnumerable<Animals> Canine(IEnumerable<Animals> 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"), plus quelques filtres. Alors maintenant, vous pouvez faire ceci:

var Leopards = Feline(AllSpotted());
var Hyenas = Canine(AllSpotted());

Donc, est-il plus rapide d’utiliser List sur IEnumerable? Seulement si vous voulez empêcher une requête d'être exécutée 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 chaque requête SQL, et la base de données ne renvoie que les lignes pertinentes. Mais si nous avions renvoyé une liste de AllSpotted(), elle risque de s'exécuter plus lentement car la base de données pourrait renvoyer beaucoup plus de données que nécessaire, et nous gaspillerions des cycles de filtrage dans le client.

Dans un programme, il peut être préférable de différer la conversion de votre requête en une liste jusqu'à la fin, donc si je vais énumérer plusieurs fois par Léopards et Hyènes, je le ferais:

List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();
679
Chris Wenham

Il y a un très bon article écrit par: Le TechBlog de Claudio Bernasconi ici: Quand utiliser IEnumerable, ICollection, IList et List

Voici quelques informations de base sur les scénarios et les fonctions:

enter image description hereenter image description here

139
rubStackOverflow

Une classe qui implémente IEnumerable vous permet d'utiliser la syntaxe foreach.

En gros, il existe une méthode pour obtenir le prochain élément de la collection. Il n'a pas besoin que toute la collection soit en mémoire ni le nombre d'éléments qu'il contient. foreach continue à obtenir l'élément suivant jusqu'à épuisement.

Cela peut être très utile dans certaines circonstances, par exemple, dans une table de base de données volumineuse, vous ne voulez pas tout copier en mémoire avant de commencer à traiter les lignes.

Maintenant 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, elle s'exécute lorsque vous effectuez une itération avec foreach. Une instruction IEnumerable linq s'exécute lorsque vous effectuez une itération de foreach, mais vous pouvez le forcer à itérer plus tôt à l'aide de .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 ) ...
124
Keith

Personne n'a mentionné une différence cruciale, a répondu ironiquement à une question fermée en tant que duplicata.

IEnumerable est en lecture seule et List n'est pas.

Voir Différence pratique entre List et IEnumerable

78
CAD bloke

La chose la plus importante à réaliser est que, avec Linq, la requête n'est pas évaluée immédiatement. Il est uniquement exécuté dans le cadre d'une itération à travers le IEnumerable<T> résultant dans un foreach - c'est ce que font tous les délégués étranges.

Ainsi, le premier exemple évalue la requête immédiatement en appelant ToList et en plaçant les résultats de la requête dans une liste.
Le deuxième exemple renvoie un IEnumerable<T> contenant toutes les informations nécessaires pour exécuter la requête ultérieurement.

En termes de performances, la réponse est cela dépend. Si vous souhaitez que les résultats soient évalués immédiatement (par exemple, vous modifiez les structures que vous interrogez ultérieurement, ou si vous ne souhaitez pas que l'itération sur le IEnumerable<T> prenne beaucoup de temps), utilisez un liste. Sinon, utilisez un IEnumerable<T>. La valeur par défaut devrait être d'utiliser l'évaluation à la demande dans le deuxième exemple, car elle utilise généralement moins de mémoire, à moins d'une raison spécifique pour stocker les résultats dans une liste.

65
thecoop

L'avantage d'IEnumerable est l'exécution différée (généralement avec des bases de données). La requête ne sera exécutée que lorsque vous parcourez les données. C'est une requête qui attend d'être nécessaire (chargement paresseux).

Si vous appelez ToList, la requête sera exécutée ou "matérialisée", comme je me plais à le dire.

Il y a des avantages et des inconvénients à la fois. Si vous appelez ToList, vous pouvez supprimer un mystère sur le moment où la requête est exécutée. Si vous vous en tenez à IEnumerable, vous aurez l’avantage que le programme ne fonctionne pas tant que ce n’est pas réellement requis.

38
Matt Sherman

Je vais partager un concept mal utilisé dans lequel je suis tombé un jour:

var names = new List<string> {"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() );

Résultat attendu

// I was expecting    
print( startingWith_M.ToList() ); // mercedes, mazda
print( startingWith_F.ToList() ); // fiat, ferrari

Résultat actuel

// what printed actualy   
print( startingWith_M.ToList() ); // mazda
print( startingWith_F.ToList() ); // ford, fiat, ferrari

Explication

Comme dans d’autres réponses, l’évaluation du résultat a été différée jusqu’à l’appel de ToList ou d’autres méthodes d’appel similaires, par exemple ToArray.

Je peux donc réécrire le code dans ce cas comme suit:

var names = new List<string> {"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() );

Jouer autour

https://repl.it/E8Ki/

18
amd

Si vous ne voulez que les énumérer, utilisez le IEnumerable.

Attention, changer la collection d'origine en cours d'énumération est une opération dangereuse. Dans ce cas, vous voudrez commencer par ToList. Cela créera un nouvel élément de liste pour chaque élément en mémoire, énumérant la IEnumerable et sera donc moins performant si vous n’enumérisez qu’une seule fois - mais les méthodes plus sûres et parfois List sont pratiques (par exemple en accès aléatoire ).

15
Daren Thomas

En plus de toutes les réponses affichées ci-dessus, voici mes deux sous. Il existe de nombreux autres types que List qui implémente IEnumerable, tels que ICollection, ArrayList, etc. Ainsi, si nous avons IEnumerable comme paramètre d'une méthode, nous pouvons transmettre tous les types de collection à la fonction. C'est-à-dire que nous pouvons avoir une méthode pour utiliser l'abstraction et non une implémentation spécifique.

5
Ananth

Il existe de nombreux cas (tels qu'une liste infinie ou une très grande liste) où IEnumerable ne peut pas être transformé en liste. Les exemples les plus évidents sont tous les nombres premiers, tous les utilisateurs de Facebook avec leurs détails, ou tous les éléments sur eBay.

La différence est que les objets "List" sont stockés "ici et maintenant", alors que les objets "IEnumerable" fonctionnent "un à la fois". Donc, si je passe en revue tous les éléments sur ebay, un à la fois serait une chose que même un petit ordinateur peut gérer, mais ".ToList ()" me manquerait sûrement de mémoire, quelle que soit la taille de mon ordinateur. Aucun ordinateur ne peut à lui seul contenir et gérer une telle quantité de données.

[Edit] - Inutile de dire - ce n'est pas "ni ceci ni cela". il serait souvent logique d'utiliser à la fois une liste et un IEnumerable dans la même classe. Aucun ordinateur au monde ne peut répertorier tous les nombres premiers, car, par définition, cela nécessiterait une quantité de mémoire infinie. Mais vous pouvez facilement penser à un class PrimeContainer contenant un IEnumerable<long> primes, qui, pour des raisons évidentes, contient également un SortedList<long> _primes. tous les nombres premiers calculés jusqu'à présent. le prochain nombre premier à vérifier ne sera exécuté qu'avec les nombres premiers existants (jusqu'à la racine carrée). De cette façon, vous obtenez les deux nombres premiers - un à la fois (IEnumerable) et une bonne liste de "nombres premiers jusqu'à présent", ce qui est une assez bonne approximation de la liste entière (infinie).

1
LongChalk