Pourquoi devrais-je utiliser List <T> sur IEnumerable <T>?

24

Dans mon application Web ASP.net MVC4, j'utilise IEnumerables, en essayant de suivre le mantra pour programmer l'interface, pas l'implémentation.

Return IEnumerable(Of Student)

contre

Return New List(Of Student)

Les gens me disent d'utiliser List et non IEnumerable, car les listes forcent l'exécution de la requête et IEumerable ne le fait pas.

Est-ce vraiment la meilleure pratique? Y a-t-il une alternative? Je me sens étrange en utilisant des objets concrets où une interface pourrait être utilisée. Mon étrange sentiment est-il justifié?

Rowan Freeman
la source
2
Euh, d'abord, pourquoi est-ce une bonne chose de forcer l'exécution de la requête? Deuxièmement, vous devez surveiller et profiler les appels de la base de données pour évaluer si cette considération technique est valable.
user16764
2
Il est dit que toutes les requêtes doivent être exécutées afin que le modèle soit terminé et chargé prêt pour la vue. C'est-à-dire que la vue doit recevoir tout et ne pas interroger la base de données.
Rowan Freeman
3
Cette question StackOverflow le couvre assez bien.
Karl Bielefeldt
C'est une bonne réponse, mais je veux savoir sa pertinence pour MVC. Pourquoi la vue ne peut-elle pas recevoir IEnumerables pour que toutes les requêtes soient exécutées juste à temps?
Rowan Freeman
2
"Il est dit que toutes les requêtes doivent être exécutées afin que le modèle soit terminé et chargé prêt pour la vue. C'est-à-dire que la vue doit recevoir tout et ne pas interroger la base de données.". C'est absurde. Si vous passez un IEnumerable, votre vue ne sait pas ou ne se soucie pas si elle interroge la base de données. Et c'est comme ça que ça devrait être.
user16764

Réponses:

21

Il ToList()peut arriver que faire une requête sur vos requêtes linq soit important pour vous assurer que vos requêtes s'exécutent au moment et dans l'ordre que vous attendez. Ces scénarios sont cependant rares et rien ne devrait trop inquiéter jusqu'à ce qu'ils se heurtent vraiment à eux.

Pour faire court, utilisez IEnumerablechaque fois que vous n'avez besoin que d'itération, utilisez IListlorsque vous avez besoin d'indexer directement et avez besoin d'un tableau de taille dynamique (si vous avez besoin d'indexer sur un tableau de taille fixe, utilisez simplement un tableau standard).

En ce qui concerne le temps d'exécution, vous pouvez toujours utiliser une liste comme IEnumerablevariable, alors n'hésitez pas à retourner un IEnumerableen faisant un .ToList();, ou à passer un paramètre en tant que IEnumerableen exécutant .ToList()sur leIEnumerable pour forcer l'exécution immédiatement et là. Veillez simplement à ce que chaque fois que vous forcez l'exécution avec .ToList()vous ne vous accrochez pas à la IEnumerablevariable que vous venez de faire et l'exécutez à nouveau, sinon vous finirez par doubler inutilement les itérations de votre requête LINQ.

En ce qui concerne MVC, il n'y a vraiment rien de spécial à noter ici. Il va suivre les mêmes règles de temps d'exécution que le reste de .NET, je pense que vous pourriez avoir quelqu'un qui a été mordu par la confusion causée par la sémantique d'exécution retardée dans le passé et blâmé MVC en vous disant que cela est en quelque sorte lié, mais c'est ne pas. La sémantique d'exécution retardée déroute tout le monde au début (et même pendant un bon moment par la suite; elles peuvent être délicates). Encore une fois cependant, ne vous inquiétez pas jusqu'à ce que vous vous souciez vraiment de garantir qu'une requête LINQ ne soit pas exécutée deux fois ou qu'elle ne soit pas exécutée dans un certain ordre par rapport à un autre code, auquel cas attribuez-vous votre variable à elle-même. pour forcer l'exécution et tout ira bien.

Jimmy Hoffa
la source
Est-il mauvais de donner une vue IEnumerables? Devriez-vous lui donner des listes afin que les requêtes aient été exécutées au moment où la vue les obtient?
Rowan Freeman
@RowanFreeman Je viens d'ajouter une modification pour répondre à cette question. Je pense que vous avez quelqu'un qui a rencontré quelque chose qu'ils ne comprenaient pas entièrement (ne peut pas leur en vouloir, l'exécution retardée est sérieusement complexe et déroutante) et l'a attribué à un mauvais joojoo au lieu de travailler pour comprendre le comportement complet de l'exécution retardée sémantique.
Jimmy Hoffa
Bonne réponse. Est-ce que je devrai jamais réellement utiliser .ToList ()? Jusqu'à présent, mon application fonctionne correctement en utilisant uniquement IEnumerables et en les passant du modèle à la vue. Aucune liste ou .ToList (). Ma question n'est pas une question de fonctionnalité - je sais que mon application fonctionne. Ma question est l'une des meilleures pratiques.
Rowan Freeman du
4
La meilleure pratique @RowanFreeman consiste à utiliser l'interface minimale qui répond toujours à vos besoins. IEnumerable le fait en ce moment pour vous, alors ne vous inquiétez pas de le changer. Cela dit, un jour viendra où vous aurez une requête exécutée 3 ou 10 fois et ne comprendrez pas pourquoi, ou attendez-vous à ce qu'une requête s'exécute avant une insertion pour ensuite la trouver s'exécute ensuite, ce sont les temps que vous devez reconnaître. () forcera l'exécution quand vous le voudrez, et réitérer un IEnumerable à partir d'une requête LINQ plusieurs fois exécutera la requête entière plusieurs fois; Corrigez votre exécution lorsque ces événements se produisent
Jimmy Hoffa
1
Vous ne devriez même pas utiliser List--passer autour d'un Listimplique que le contenu de la liste va être modifié. Si vous souhaitez renvoyer une collection, utilisez IReadOnlyCollection. Listest à utiliser dans les méthodes et à échanger entre les méthodes qui modifient la liste. C'est ça!
ErikE
7

Il y a deux problèmes.

IENumerable<Data> query = MyQuery();

//Later
foreach (Data item in query) {
  //Process data
}

Au moment où la boucle "Process Data" est atteinte, la requête peut ne plus être valide. Par exemple, si la requête est exécutée sur un DataContext qui a déjà été supprimé, votre code lèvera une exception. Ce genre de chose devient très déroutant lorsque vous traitez une requête dans un contexte différent de celui où vous l'avez créée.

Un problème secondaire est que votre connexion ne sera pas libérée tant que la boucle "Process Data" ne sera pas terminée. Ce n'est un problème que si les «données de processus» sont complexes. Ceci est mentionné à http://msdn.microsoft.com/en-us/library/bb386929.aspx :

Q. Combien de temps ma connexion à la base de données reste-t-elle ouverte?

A. Une connexion reste généralement ouverte jusqu'à ce que vous consommiez les résultats de la requête. Si vous prévoyez de prendre du temps pour traiter tous les résultats et que vous n'êtes pas opposé à la mise en cache des résultats, appliquez ToList à la requête. Dans les scénarios courants où chaque objet n'est traité qu'une seule fois, le modèle de streaming est supérieur à la fois dans DataReader et LINQ to SQL.

Donc, ces problèmes sont pourquoi vous êtes encouragés à vous assurer que la requête est réellement exécutée, par exemple en appelant ToList(). Cependant, comme suggère Jimmy , rien ne vous empêche de renvoyer votre liste en tant qu'IEnumerable.

En règle générale, je recommande d'éviter d'itérer plusieurs fois sur un IEnumerable. En supposant que les consommateurs de votre code suivent cette règle, je ne considère pas que quelqu'un puisse frapper la base de données deux fois en exécutant la requête deux fois.

Brian
la source
1

Un autre avantage d’énumérer les IEnumerable précoce est que les exceptions seront levées à l'endroit approprié. Cela facilite le débogage.

Par exemple, si vous avez obtenu une exception de blocage dans l'une de vos vues Razor, ce ne serait pas vraiment aussi clair que si l'exception s'était produite pendant l'une de vos méthodes d'accès aux données.

Sam
la source
Toute méthode renvoyant un IEnumerablequi peut lancer est probablement une erreur. Les IEnumerableméthodes différées doivent être divisées en deux: une méthode non différée vérifie les paramètres et configure, en lançant si nécessaire (par exemple, en raison d'un argument nul). Il renvoie ensuite un appel à l'implémentation privée qui est différé. Je ne pense pas que mes commentaires soient complètement en contradiction avec votre réponse, mais pensez que vous avez omis un aspect important dans votre réponse, qui est la signification sémantique de l'utilisation IEnumerablevs a List(mutation) vs IReadOnlyCollection(aucun avantage pour report) .
ErikE