Ich habe einige Zweifel darüber, wie Enumerators und LINQ funktionieren. Betrachten Sie diese zwei einfachen Auswahlen:
List<Animal> sel = (from animal in Animals
join race in Species
on animal.SpeciesKey equals race.SpeciesKey
select animal).Distinct().ToList();
oder
IEnumerable<Animal> sel = (from animal in Animals
join race in Species
on animal.SpeciesKey equals race.SpeciesKey
select animal).Distinct();
Ich habe die Namen meiner ursprünglichen Objekte geändert, damit dies wie ein allgemeineres Beispiel aussieht. Die Abfrage selbst ist nicht so wichtig. Was ich fragen möchte, ist Folgendes:
foreach (Animal animal in sel) { /*do stuff*/ }
Ich habe festgestellt, dass wenn ich IEnumerable
verwende, wenn ich "sel", das in diesem Fall die IEnumerable ist, debugge und inspiziere, es einige interessante Mitglieder hat: "inner", "outer", "innerKeySelector" und "outerKeySelector" scheinen diese letzten 2 Delegierten zu sein. Das "innere" Element enthält keine "Animal" -Instanzen, sondern "Species" -Instanzen, was für mich sehr seltsam war. Das "äußere" Element enthält "Tier" -Instanzen. Ich nehme an, dass die beiden Delegierten bestimmen, was rein und was raus geht.
Ich habe festgestellt, dass bei Verwendung von "Distinct" das "innere" 6 Elemente enthält (dies ist falsch, da nur 2 Distinct sind), das "äußere" jedoch die korrekten Werte enthält. Auch hier bestimmen wahrscheinlich die delegierten Methoden dies, aber dies ist etwas mehr, als ich über IEnumerable weiß.
Welche der beiden Optionen ist am leistungsstärksten?
Die Konvertierung der bösen Liste über .ToList()
?
Oder verwenden Sie den Enumerator direkt?
Wenn Sie können, erklären Sie bitte auch ein wenig oder werfen Sie einige Links, die diese Verwendung von IEnumerable erklären.
IEnumerable
beschreibt das Verhalten, während List eine Implementierung dieses Verhaltens ist. Wenn Sie IEnumerable
verwenden, können Sie dem Compiler die Möglichkeit geben, die Arbeit auf einen späteren Zeitpunkt zu verschieben und möglicherweise auf diese Weise zu optimieren. Wenn Sie ToList () verwenden, erzwingen Sie, dass der Compiler die Ergebnisse sofort überprüft.
Immer, wenn ich LINQ-Ausdrücke "stapele", verwende ich IEnumerable
, da ich LINQ die Möglichkeit gebe, die Auswertung zu verschieben und möglicherweise das Programm zu optimieren, indem ich nur das Verhalten spezifiziere. Erinnern Sie sich, wie LINQ die SQL zum Abfragen der Datenbank erst generiert, wenn Sie sie aufgelistet haben? Bedenken Sie:
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;
}
Jetzt haben Sie eine Methode, mit der Sie ein erstes Sample auswählen ("AllSpotted"), sowie einige Filter. Nun können Sie dies tun:
var Leopards = Feline(AllSpotted());
var Hyenas = Canine(AllSpotted());
Ist es also schneller, List über IEnumerable
zu verwenden? Nur wenn Sie verhindern möchten, dass eine Abfrage mehrmals ausgeführt wird. Aber ist es insgesamt besser? Im obigen Beispiel werden Leoparden und Hyänen in jeweils einzelne SQL-Abfragen konvertiert, und die Datenbank gibt nur die Zeilen zurück, die relevant sind. Wenn wir jedoch eine Liste von AllSpotted()
zurückgegeben hätten, könnte sie langsamer ausgeführt werden, da die Datenbank weitaus mehr Daten zurückgeben könnte, als tatsächlich benötigt werden, und wir verschwenden Zyklen mit der Filterung im Client.
In einem Programm ist es möglicherweise besser, die Konvertierung Ihrer Abfrage in eine Liste bis zum Ende zu verschieben. Wenn ich also Leoparden und Hyänen mehrmals aufzählen möchte, würde ich Folgendes tun:
List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();
Es gibt einen sehr guten Artikel von: Claudio Bernasconis TechBlog hier: Verwendung von IEnumerable, ICollection, IList und List
Hier einige grundlegende Punkte zu Szenarien und Funktionen:
Mit einer Klasse, die IEnumerable
implementiert, können Sie die foreach
-Syntax verwenden.
Grundsätzlich gibt es eine Methode, um das nächste Element in der Sammlung abzurufen. Es ist nicht erforderlich, dass sich die gesamte Sammlung im Speicher befindet, und es ist nicht bekannt, wie viele Elemente sich darin befinden. foreach
ruft nur das nächste Element ab, bis es leer ist.
Dies kann unter bestimmten Umständen sehr nützlich sein, z. B. wenn Sie in einer umfangreichen Datenbanktabelle nicht das gesamte Objekt in den Speicher kopieren möchten, bevor Sie mit der Verarbeitung der Zeilen beginnen.
Jetzt implementiert List
IEnumerable
, repräsentiert aber die gesamte Sammlung im Speicher. Wenn Sie ein IEnumerable
haben und .ToList()
aufrufen, erstellen Sie eine neue Liste mit dem Inhalt der Aufzählung im Speicher.
Ihr linq-Ausdruck gibt eine Aufzählung zurück, und der Ausdruck wird standardmäßig ausgeführt, wenn Sie mit foreach
iterieren. Eine IEnumerable
linq-Anweisung wird ausgeführt, wenn Sie foreach
durchlaufen, Sie können sie jedoch mit .ToList()
zu einer schnelleren Iteration zwingen.
Folgendes meine ich:
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 ) ...
Niemand erwähnte einen entscheidenden Unterschied, ironischerweise beantwortet auf eine Frage, die als ein Duplikat davon geschlossen wurde.
IEnumerable ist schreibgeschützt und List nicht.
Das Wichtigste ist, dass die Abfrage mit Linq nicht sofort ausgewertet wird. Es wird nur als Teil der Iteration durch den resultierenden IEnumerable<T>
in einem foreach
ausgeführt - das ist, was alle seltsamen Delegierten tun.
Das erste Beispiel wertet die Abfrage also sofort aus, indem es ToList
aufruft und die Abfrageergebnisse in eine Liste schreibt.
Das zweite Beispiel gibt einen IEnumerable<T>
zurück, der alle Informationen enthält, die zum späteren Ausführen der Abfrage erforderlich sind.
In Bezug auf die Leistung lautet die Antwort es kommt darauf an. Wenn die Ergebnisse sofort ausgewertet werden müssen (z. B. wenn Sie die Strukturen, die Sie später abfragen, mutieren oder wenn die Iteration über den IEnumerable<T>
nicht lange dauern soll), verwenden Sie a Liste. Verwenden Sie andernfalls einen IEnumerable<T>
. Die Standardeinstellung sollte die On-Demand-Auswertung im zweiten Beispiel sein, da diese im Allgemeinen weniger Speicher benötigt, es sei denn, es gibt einen bestimmten Grund, die Ergebnisse in einer Liste zu speichern.
Der Vorteil von IEnumerable ist die verzögerte Ausführung (normalerweise bei Datenbanken). Die Abfrage wird erst ausgeführt, wenn Sie die Daten tatsächlich durchlaufen haben. Es ist eine Abfrage, die wartet, bis sie benötigt wird (auch bekannt als Lazy Loading).
Wenn Sie ToList aufrufen, wird die Abfrage ausgeführt oder "materialisiert", wie ich sagen möchte.
Beide haben Vor- und Nachteile. Wenn Sie ToList aufrufen, können Sie einige Rätsel beseitigen, wann die Abfrage ausgeführt wird. Wenn Sie sich an IEnumerable halten, haben Sie den Vorteil, dass das Programm erst dann funktioniert, wenn es tatsächlich benötigt wird.
Ich werde ein missbrauchtes Konzept teilen, in das ich eines Tages verfallen bin:
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() );
// 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
Gemäß anderen Antworten wurde die Auswertung des Ergebnisses verschoben, bis ToList
oder ähnliche Aufrufmethoden aufgerufen wurden, zum Beispiel ToArray
.
Daher kann ich den Code in diesem Fall wie folgt umschreiben:
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() );
Wenn Sie sie nur aufzählen möchten, verwenden Sie IEnumerable
.
Beachten Sie jedoch, dass das Ändern der zu zählenden Originalsammlung eine gefährliche Operation ist. In diesem Fall möchten Sie zunächst ToList
. Dadurch wird für jedes Element im Speicher ein neues Listenelement erstellt, das IEnumerable
auflistet. Wenn Sie nur einmal auflisten, ist dies weniger performant - aber sicherer und manchmal sind die List
-Methoden praktisch (z. B. beim wahlfreien Zugriff) ).
Zusätzlich zu all den oben genannten Antworten sind hier meine zwei Cent. Es gibt viele andere Typen als List, die IEnumerable wie ICollection, ArrayList usw. implementieren. Wenn wir also IEnumerable als Parameter einer Methode haben, können wir der Funktion beliebige Auflistungstypen übergeben. Das heißt, wir können eine Methode haben, um mit der Abstraktion zu arbeiten, nicht mit einer bestimmten Implementierung.
Es gibt viele Fälle (wie eine unendliche Liste oder eine sehr große Liste), in denen IEnumerable nicht in eine Liste umgewandelt werden kann. Die offensichtlichsten Beispiele sind alle Primzahlen, alle Facebook-Nutzer mit ihren Details oder alle Artikel bei ebay.
Der Unterschied besteht darin, dass "Listen" -Objekte "genau hier und jetzt" gespeichert werden, während "IEnumerable" -Objekte "nur nacheinander" funktionieren. Wenn ich also alle Artikel bei ebay durchgehen würde, wäre einer nach dem anderen etwas, das sogar ein kleiner Computer handhaben kann, aber ".ToList ()" würde mir mit Sicherheit den Speicher entziehen, egal wie groß mein Computer war. Kein Computer kann für sich genommen so viele Daten enthalten und verarbeiten.
[Bearbeiten] - Unnötig zu sagen - es ist nicht "entweder dies oder das". Oft ist es sinnvoll, sowohl eine Liste als auch eine IEnumerable in derselben Klasse zu verwenden. Kein Computer auf der Welt könnte alle Primzahlen auflisten, da dies per Definition unendlich viel Speicherplatz erfordern würde. Man könnte sich aber leicht einen class PrimeContainer
vorstellen, der einen IEnumerable<long> primes
enthält, der aus offensichtlichen Gründen auch einen SortedList<long> _primes
enthält. alle bisher berechneten Primzahlen. Die nächste zu überprüfende Primzahl würde nur gegen die vorhandenen Primzahlen (bis zur Quadratwurzel) laufen. Auf diese Weise erhalten Sie sowohl Primzahlen nacheinander (IEnumerable) als auch eine gute Liste von "Primzahlen", was eine ziemlich gute Annäherung an die gesamte (unendliche) Liste darstellt.