Warum sollte ich List <T> anstelle von IEnumerable <T> verwenden?

24

In meiner ASP.net MVC4-Webanwendung verwende ich IEnumerables und versuche, dem Mantra zu folgen, um die Schnittstelle zu programmieren, nicht die Implementierung.

Return IEnumerable(Of Student)

vs

Return New List(Of Student)

Ich soll List und nicht IEnumerable verwenden, da Listen die Ausführung der Abfrage erzwingen und IEumerable nicht.

Ist das wirklich die beste Vorgehensweise? Gibt es eine Alternative? Ich fühle mich seltsam, wenn ich konkrete Objekte verwende, bei denen eine Schnittstelle verwendet werden könnte. Ist mein seltsames Gefühl berechtigt?

Rowan Freeman
quelle
2
Erstens, warum ist es eine gute Sache, die Ausführung der Abfrage zu erzwingen? Zweitens sollten Sie die Datenbankaufrufe überwachen und profilieren, um zu bewerten, ob diese technische Überlegung von Nutzen ist.
user16764
2
Es heißt, dass alle Abfragen ausgeführt werden sollten, damit das Modell fertig ist und für die Ansicht geladen werden kann. Dh die Ansicht sollte alles empfangen und nicht die Datenbank abfragen.
Rowan Freeman
3
Diese StackOverflow-Frage deckt es ziemlich gut ab.
Karl Bielefeldt
Das ist eine gute Antwort, aber ich möchte wissen, ob es für MVC relevant ist. Warum kann die Ansicht nicht mit IEnumerables versehen werden, sodass alle Abfragen just-in-time ausgeführt werden?
Rowan Freeman
2
"Es heißt, dass alle Abfragen ausgeführt werden sollten, damit das Modell fertig ist und für die Ansicht geladen werden kann. Das heißt, die Ansicht sollte alles empfangen und nicht die Datenbank abfragen." Das ist Unsinn. Wenn Sie eine IEnumerable übergeben, weiß Ihre Ansicht nicht, ob die Datenbank abgefragt wird. Und so sollte es auch sein.
user16764

Antworten:

21

Es gibt ToList()Situationen, in denen es wichtig sein kann, eine linq-Abfrage durchzuführen, um sicherzustellen, dass Ihre Abfragen zum gewünschten Zeitpunkt und in der von Ihnen erwarteten Reihenfolge ausgeführt werden. Diese Szenarien sind jedoch selten und nichts, worüber man sich zu viele Sorgen machen sollte, bis sie tatsächlich auf sie treffen.

Um es kurz zu machen: Verwenden IEnumerableSie diese Option immer dann , wenn Sie nur Iterationen benötigen, IListwenn Sie direkt indizieren müssen und ein Array mit dynamischer Größe benötigen (wenn Sie ein Array mit fester Größe indizieren müssen, verwenden Sie einfach ein Standardarray).

Wie für die Ausführungszeit Sache, können Sie immer eine Liste als verwenden IEnumerableVariable, so fühlen sich frei , eine Rückkehr IEnumerableein , indem Sie .ToList();oder in einem Parameter , der als einen Pass IEnumerabledurch die Ausführung .ToList()auf der IEnumerablerechten dann Kraft Ausführung und dort. Achten Sie nur darauf, dass Sie bei jeder erzwungenen Ausführung .ToList()nicht an der IEnumerableVariablen festhalten, mit der Sie dies gerade getan haben, und sie erneut ausführen. Andernfalls werden Sie die Iterationen in Ihrer LINQ-Abfrage unnötigerweise verdoppeln.

In Bezug auf MVC gibt es hier wirklich nichts Besonderes zu beachten. Es wird den gleichen Regeln für die Ausführungszeit folgen wie der Rest von .NET. Ich glaube, Sie haben jemanden, der durch die verzögerte Ausführungssemantik in der Vergangenheit verwirrt war und MVC die Schuld dafür gibt, dass dies irgendwie verwandt ist, aber es ist nicht. Die Semantik der verzögerten Ausführung verwirrt zunächst alle (und auch noch für eine Weile danach; sie kann etwas schwierig sein). Machen Sie sich aber auch hier keine Sorgen, bis Sie wirklich sicherstellen möchten, dass eine LINQ-Abfrage nicht zweimal ausgeführt wird oder in einer bestimmten Reihenfolge im Vergleich zu anderem Code ausgeführt werden muss. Weisen Sie dann Ihre Variable sich selbst zu. ToList () Hinrichtung zu erzwingen, und Sie werden in Ordnung sein.

Jimmy Hoffa
quelle
Ist es schlecht, eine Ansicht IEnumerables zu geben? Sollten Sie Listen angeben, damit die Abfragen ausgeführt wurden, wenn die Ansicht sie erhält?
Rowan Freeman
@RowanFreeman Ich habe gerade eine Bearbeitung hinzugefügt, um dies zu beantworten. Ich denke, Sie haben jemanden, der auf etwas gestoßen ist, das er nicht ganz verstanden hat (kann ihn nicht beschuldigen, die verzögerte Ausführung ist sehr komplex und verwirrend) und der es zu einem schlechten Joojoo erklärt hat, anstatt daran zu arbeiten, das gesamte Verhalten der verzögerten Ausführung zu verstehen Semantik.
Jimmy Hoffa
Gute Antwort. Muss ich also jemals .ToList () verwenden? Bisher funktioniert meine Anwendung einwandfrei, wenn nur IEnumerables verwendet und vom Modell an die Ansicht übergeben werden. Keine Liste oder .ToList (). Meine Frage betrifft nicht die Funktionalität - ich weiß, dass meine Anwendung funktioniert. Meine Frage ist eine der besten Praktiken.
Rowan Freeman
4
@RowanFreeman empfiehlt, die minimale Benutzeroberfläche zu verwenden, die noch Ihren Anforderungen entspricht. IEnumerable erledigt dies gerade für Sie. Machen Sie sich also keine Sorgen, wenn Sie es ändern möchten. Das heißt, es wird ein Tag kommen, an dem eine Abfrage drei- oder zehnmal ausgeführt wird und Sie nicht verstehen, warum eine Abfrage ausgeführt werden soll, bevor eine Einfügung ausgeführt wird. Dies sind die Zeiten, an denen Sie .ToList erkennen müssen () erzwingt die Ausführung, wenn Sie möchten, und das mehrmalige Wiederholen einer IEnumerable aus einer LINQ-Abfrage führt die gesamte Abfrage mehrmals aus. Korrigieren Sie Ihre Hinrichtung, wenn diese Ereignisse eintreten
Jimmy Hoffa
1
Sie sollten List--passing a nicht einmal verwenden, um zu Listimplizieren, dass der Inhalt der Liste geändert wird. Wenn Sie eine Sammlung zurückgeben möchten, verwenden Sie IReadOnlyCollection. ListDient zur Verwendung in Methoden und zum Austausch zwischen Methoden, die die Liste ändern. Das ist es!
ErikE
7

Es gibt zwei Probleme.

IENumerable<Data> query = MyQuery();

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

Wenn die "Process Data" -Schleife erreicht ist, ist die Abfrage möglicherweise nicht mehr gültig. Wenn die Abfrage beispielsweise für einen DataContext ausgeführt wird, der bereits freigegeben wurde, löst Ihr Code eine Ausnahme aus. Dies ist sehr verwirrend, wenn Sie eine Abfrage in einem anderen Kontext als dem, in dem Sie sie erstellt haben, verarbeiten.

Ein sekundäres Problem ist, dass Ihre Verbindung nicht freigegeben wird, bis die Schleife "Process Data" abgeschlossen ist. Dies ist nur dann ein Problem, wenn "Process Data" komplex ist. Dies wird unter http://msdn.microsoft.com/en-us/library/bb386929.aspx erwähnt :

F. Wie lange bleibt meine Datenbankverbindung offen?

A. Eine Verbindung bleibt normalerweise offen, bis Sie die Abfrageergebnisse verbrauchen. Wenden Sie ToList auf die Abfrage an, wenn Sie erwarten, dass Sie Zeit benötigen, um alle Ergebnisse zu verarbeiten, und wenn Sie nicht dagegen sind, die Ergebnisse zwischenzuspeichern. In gängigen Szenarien, in denen jedes Objekt nur einmal verarbeitet wird, ist das Streaming-Modell sowohl in DataReader als auch in LINQ to SQL überlegen.

Aus diesen Gründen sollten Sie sicherstellen, dass die Abfrage tatsächlich ausgeführt wird, z ToList(). B. durch einen Anruf . Wie Jimmy vorschlägt, hindert Sie nichts daran, Ihre Liste als IEnumerable zurückzugeben.

Im Allgemeinen empfehle ich, das mehrmalige Durchlaufen eines IEnumerable zu vermeiden. Unter der Annahme, dass die Konsumenten Ihres Codes diese Regel befolgen, ist es meines Erachtens nicht bedenklich, dass jemand die Datenbank zweimal durch zweimaliges Ausführen der Abfrage erreicht.

Brian
quelle
1

Ein weiterer Vorteil der Aufzählung der IEnumerablefrühen Ausnahmen ist, dass sie an der entsprechenden Stelle ausgelöst werden. Dies unterstützt das Debuggen.

Wenn Sie beispielsweise in einer Ihrer Razor-Ansichten eine Deadlock-Ausnahme festgestellt haben, ist dies nicht so eindeutig, als ob die Ausnahme während einer Ihrer Datenzugriffsmethoden aufgetreten wäre.

Sam
quelle
Jede Methode, die einen IEnumerablezurückgibt, der werfen kann, macht wahrscheinlich einen Fehler. Verzögerte IEnumerableMethoden sollten in zwei aufgeteilt werden: Eine nicht verzögerte Methode prüft die Parameter und richtet sie ein und löst sie gegebenenfalls aus (z. B. aufgrund eines Null-Arguments). Dann gibt er einen Aufruf an die privaten Implementierung , die sich verschoben. Ich glaube nicht , meine Kommentare in völligem Widerspruch zu Ihrer Antwort sind, aber denke , dass Sie in Ihrer Antwort einen wichtigen Aspekt ausgelassen haben, die das ist semantische Bedeutung der Verwendung IEnumerablegegen ein List(Mutation) vs. IReadOnlyCollection(keinen Nutzen Aufschub) .
ErikE