854 votes

IEnumerable vs Liste - Que faut-il utiliser ? Comment fonctionnent-ils ?

J'ai quelques doutes sur le fonctionnement des énumérateurs et de LINQ. Considérons 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 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 est ceci :

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

  2. J'ai remarqué que si j'utilise "Distinct", l'élément "inner" contient 6 éléments (ce qui est incorrect car seuls 2 sont Distinct), mais l'élément "outer" contient les valeurs correctes. Encore une fois, ce sont probablement les méthodes déléguées qui déterminent cela, mais c'est un peu plus que ce que je connais de IEnumerable.

  3. Plus important encore, laquelle des deux options est la plus performante ?

La conversion de la Liste du mal via .ToList() ?

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

Si vous le pouvez, veuillez également expliquer un peu ou fournir des liens qui expliquent cette utilisation de IEnumerable.

915voto

C. Lawrence Wenham Points 11271

IEnumerable décrit un comportement, tandis que List est une mise en œuvre de ce comportement. Lorsque vous utilisez IEnumerable vous donnez au compilateur la possibilité de reporter le travail à plus tard, en l'optimisant éventuellement en cours de route. Si vous utilisez ToList(), vous forcez le compilateur à réifier les résultats tout de suite.

Chaque fois que je "empile" des expressions LINQ, j'utilise IEnumerable En effet, en spécifiant uniquement le comportement, je donne à LINQ une chance de différer l'évaluation et éventuellement d'optimiser le programme. Vous vous souvenez que LINQ ne génère pas le SQL pour interroger la base de données avant que vous ne l'ayez énumérée ? Considérez 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;
}

Vous avez maintenant une méthode qui sélectionne un échantillon initial ("AllSpotted"), plus quelques filtres. Donc maintenant vous pouvez faire ceci :

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

Il est donc plus rapide d'utiliser List plutôt que IEnumerable ? Seulement si vous voulez empêcher qu'une requête soit exécutée plus d'une fois. Mais est-ce mieux dans l'ensemble ? Eh bien, dans l'exemple ci-dessus, les Léopards et les Hyènes sont convertis en des requêtes SQL uniques pour chaque et la base de données ne renvoie que les lignes qui sont pertinentes. Mais si nous avions renvoyé une liste à partir de AllSpotted() Dans ce cas, l'exécution peut être plus lente, car la base de données peut renvoyer beaucoup plus de données que nécessaire, et nous perdons des cycles à effectuer le filtrage dans le client.

Dans un programme, il peut être préférable de reporter la conversion de votre requête en liste jusqu'à la toute fin, donc si je dois énumérer les Léopards et les Hyènes plus d'une fois, je ferais ceci :

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

1 votes

Bonjour et merci d'avoir répondu ::- ). Vous m'avez donné un très bon exemple d'un cas où le cas IEnumerable est clairement avantageux en termes de performances. Une idée concernant l'autre partie de ma question ? Pourquoi l'Enumerable est "divisé" en "inner" et "outer" ? Cela se produit lorsque j'inspecte l'élément en mode débogage/rupture via la souris. Est-ce peut-être la contribution de Visual Studio ? Enumérer sur place et indiquer l'entrée et la sortie de l'Enum ?

12 votes

Je pense qu'ils font référence aux deux côtés d'une jointure. Si vous faites "SELECT * FROM Animals JOIN Species...", la partie interne de la jointure est Animals, et la partie externe est Species.

1 votes

Vous pouvez également utiliser des requêtes compilées dans LINQ pour optimiser davantage votre code si vous le souhaitez :)

158voto

Keith Points 46288

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

En gros, il a une méthode pour obtenir l'élément suivant dans la collection. Il n'a pas besoin que la collection entière soit en mémoire et ne sait pas combien d'éléments elle contient, foreach continue juste à obtenir le prochain article jusqu'à ce qu'il soit épuisé.

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

Maintenant List met en œuvre IEnumerable mais représente l'ensemble de la collection en mémoire. Si vous avez une IEnumerable et 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 à l'aide de la commande foreach . Un site IEnumerable L'instruction linq s'exécute lorsque vous itérez le fichier foreach mais vous pouvez le forcer à itérer plus tôt en utilisant .ToList() .

Voilà 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 ) ...

4 votes

Mais que se passe-t-il si vous exécutez un foreach sur un IEnumerable sans le convertir d'abord en liste ? Apporte-t-il toute la collection en mémoire ? Ou bien, instancie-t-il les éléments un par un, au fur et à mesure qu'il parcourt la boucle foreach ? merci

1 votes

@Pap ce dernier : il s'exécute à nouveau, rien n'est automatiquement mis en cache en mémoire.

1 votes

Il semble que la différence essentielle soit 1) que tout soit en mémoire ou non. 2) IEnumerable me permet d'utiliser foreach tandis que List ira par dire index. Maintenant, si j'aimerais connaître l'indice compte/longueur de thing au préalable, IEnumerable n'aidera pas, n'est-ce pas ?

74voto

thecoop Points 23695

La chose la plus importante à comprendre est que, avec Linq, la requête n'est pas évaluée immédiatement. Elle n'est exécutée que dans le cadre de l'itération de l'ensemble des résultats de la requête. IEnumerable<T> dans un foreach - c'est ce que font tous les délégués bizarres.

Ainsi, le premier exemple évalue la requête immédiatement en appelant ToList et de mettre les résultats de la requête dans une liste.
Le deuxième exemple renvoie un IEnumerable<T> qui contient toutes les informations nécessaires pour exécuter la requête par la suite.

En termes de performances, la réponse est cela dépend . Si vous avez besoin que les résultats soient évalués en une seule fois (par exemple, si vous modifiez les structures que vous interrogez par la suite, ou si vous ne voulez pas que l'itération sur l'objet IEnumerable<T> pour prendre beaucoup de temps) utiliser une liste. Sinon, utilisez un IEnumerable<T> . Par défaut, il convient d'utiliser l'évaluation à la demande dans le deuxième exemple, car elle utilise généralement moins de mémoire, à moins qu'il n'y ait une raison spécifique de stocker les résultats dans une liste.

0 votes

Bonjour et merci d'avoir répondu ::- ). Cela a permis de dissiper presque tous mes doutes. Savez-vous pourquoi l'Enumerable est "divisé" en "inner" et "outer" ? Cela se produit lorsque j'inspecte l'élément en mode débogage/rupture via la souris. S'agit-il d'une contribution de Visual Studio ? Enumérer sur place et indiquer l'entrée et la sortie de l'Enum ?

5 votes

C'est le Join qui fait son travail - intérieur et extérieur sont les deux côtés de la jointure. En général, ne vous préoccupez pas de ce qui se trouve réellement dans le fichier IEnumerables car il sera complètement différent de votre code réel. Ne vous préoccupez que de la sortie réelle lorsque vous itérez sur celle-ci :)

50voto

Matt Sherman Points 3874

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 n'aurez pas bouclé les données. C'est une requête qui attend jusqu'à ce qu'elle soit nécessaire (aka lazy loading).

Si vous appelez ToList, la requête sera exécutée, ou "matérialisée" comme j'aime à le dire.

Il y a du pour et du contre dans les deux cas. Si vous appelez ToList, vous pouvez lever une partie du mystère quant au moment où la requête est exécutée. Si vous vous en tenez à IEnumerable, vous avez l'avantage que le programme n'effectue aucun travail avant d'en avoir besoin.

16voto

Daren Thomas Points 26812

Si tout ce que vous voulez faire est de les énumérer, utilisez la fonction IEnumerable .

Attention, cependant, la modification de la collection originale à énumérer est une opération dangereuse - dans ce cas, vous voudrez ToList premier. Cela créera un nouvel élément de liste pour chaque élément en mémoire, en énumérant les éléments suivants IEnumerable et est donc moins performant si vous n'effectuez qu'une seule énumération - mais plus sûr et parfois le List sont pratiques (par exemple dans l'accès aléatoire).

2 votes

Je ne suis pas sûr que l'on puisse affirmer que la génération d'une liste entraîne une baisse des performances.

1 votes

Steven : en effet, comme l'ont dit thecoop et Chris, il peut parfois être nécessaire d'utiliser une liste. Dans mon cas, j'ai conclu que ce n'était pas le cas. @ Daren : que voulez-vous dire par "cela créera une nouvelle liste pour chaque élément en mémoire" ? Peut-être vouliez-vous dire une "entrée de liste" ? ::- ).

1 votes

@Axonn oui, je voulais dire entrée de liste. corrigé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X