Wenn ich Code in Visual Studio schreibe, empfiehlt mir ReSharper (Gott segne es!) Oft, meine alte for-Schleife in die kompaktere foreach-Form zu ändern.
Und oft, wenn ich diese Änderung akzeptiere, geht ReSharper einen Schritt weiter und schlägt mir vor, sie in einer glänzenden LINQ-Form erneut zu ändern.
Ich frage mich also: Gibt es bei diesen Verbesserungen einige echte Vorteile? Bei einer ziemlich einfachen Codeausführung kann ich (offensichtlich) keinen Geschwindigkeitsschub feststellen, aber ich kann feststellen, dass der Code immer weniger lesbar wird ... Also frage ich mich: Lohnt es sich?
foreach
besteht darin, Elemente aus einer Sammlung zu entfernen, während sie aufgelistet werden. In der Regel wird einefor
Schleife benötigt, um vom letzten Element an zu beginnen.Antworten:
for
gegenforeach
Es besteht die allgemeine Verwirrung, dass diese beiden Konstrukte sehr ähnlich sind und dass beide wie folgt austauschbar sind:
und:
Die Tatsache, dass beide Schlüsselwörter mit denselben drei Buchstaben beginnen, bedeutet nicht, dass sie semantisch ähnlich sind. Diese Verwirrung ist besonders für Anfänger äußerst fehleranfällig. Durch eine Sammlung zu iterieren und etwas mit den Elementen zu tun, ist erledigt mit
foreach
;for
muss und sollte nicht für diesen Zweck verwendet werden , es sei denn, Sie wissen wirklich, was Sie tun.Mal sehen, was daran falsch ist mit einem Beispiel. Am Ende finden Sie den vollständigen Code einer Demo-Anwendung, mit der die Ergebnisse gesammelt werden.
Im Beispiel laden wir einige Daten aus der Datenbank, genauer gesagt die Städte von Adventure Works, sortiert nach Namen, bevor wir auf "Boston" stoßen. Die folgende SQL-Abfrage wird verwendet:
Die Daten werden von der
ListCities()
Methode geladen , die ein zurückgibtIEnumerable<string>
. Soforeach
sieht es aus:Lassen Sie es uns mit a umschreiben
for
, vorausgesetzt, beide sind austauschbar:Beide kehren in die gleichen Städte zurück, aber es gibt einen großen Unterschied.
foreach
,ListCities()
wird einmal aufgerufen und liefert 47 Artikel.for
,ListCities()
94 mal aufgerufen und ergibt insgesamt 28.153 Artikel.Was ist passiert?
IEnumerable
ist faul . Dies bedeutet, dass die Arbeit nur in dem Moment erledigt wird, in dem das Ergebnis benötigt wird. Lazy Evaluation ist ein sehr nützliches Konzept, hat jedoch einige Einschränkungen, einschließlich der Tatsache, dass es leicht ist, die Momente zu übersehen, in denen das Ergebnis benötigt wird, insbesondere in den Fällen, in denen das Ergebnis mehrmals verwendet wird.In einem Fall von a
foreach
wird das Ergebnis nur einmal angefordert. In einem Fall von a,for
wie in dem oben falsch geschriebenen Code implementiert , wird das Ergebnis 94-mal angefordert , dh 47 × 2:Jedes Mal
cities.Count()
wird aufgerufen (47 Mal),Jedes Mal
cities.ElementAt(i)
wird aufgerufen (47 Mal).94-maliges Abfragen einer Datenbank ist schrecklich, aber nicht das Schlimmste, was passieren kann. Stellen Sie sich zum Beispiel vor, was passieren würde, wenn der
select
Abfrage eine Abfrage vorangehen würde, die auch eine Zeile in die Tabelle einfügt. Richtig, wir hättenfor
die Datenbank 2.147.483.647 Mal aufgerufen , es sei denn, sie stürzt hoffentlich vorher ab.Natürlich ist mein Code voreingenommen. Ich habe absichtlich die Faulheit von genutzt
IEnumerable
und es so geschrieben, dass es immer wieder anruftListCities()
. Man kann feststellen, dass ein Anfänger dies niemals tun wird, weil:Der
IEnumerable<T>
hat nicht die EigenschaftCount
, sondern nur die MethodeCount()
. Das Aufrufen einer Methode ist beängstigend und es ist zu erwarten, dass das Ergebnis nicht zwischengespeichert und in einemfor (; ...; )
Block nicht geeignet ist .Die Indizierung ist für
IEnumerable<T>
nicht verfügbar und es ist nicht offensichtlich, dieElementAt
LINQ-Erweiterungsmethode zu finden .Wahrscheinlich würden die meisten Anfänger das Ergebnis einfach
ListCities()
in etwas umwandeln, mit dem sie vertraut sind, wie zList<T>
.Dieser Code unterscheidet sich jedoch stark von der
foreach
Alternative. Auch hier gibt es die gleichen Ergebnisse, und dieses Mal wird dieListCities()
Methode nur einmal aufgerufen, ergibt jedoch 575 Elemente, während sie mitforeach
nur 47 Elemente ergibt .Der Unterschied besteht darin,
ToList()
dass alle Daten aus der Datenbank geladen werden. Währendforeach
nur die Städte vor "Boston" angefordert werden, müssen für die neue Stadtfor
alle Städte abgerufen und gespeichert werden. Mit 575 kurzen Zeichenfolgen macht es wahrscheinlich keinen großen Unterschied, aber was wäre, wenn wir nur wenige Zeilen aus einer Tabelle mit Milliarden von Datensätzen abrufen würden?Also, was ist das
foreach
eigentlich?foreach
ist näher an einer while-Schleife. Der Code, den ich zuvor verwendet habe:kann einfach ersetzt werden durch:
Beide produzieren die gleiche IL. Beide haben das gleiche Ergebnis. Beide haben die gleichen Nebenwirkungen. Natürlich
while
kann dies in einer ähnlichen Unendlichkeit umgeschrieben werdenfor
, aber es wäre noch länger und fehleranfällig. Es steht Ihnen frei, diejenige zu wählen, die Sie besser lesen können.Willst du es selbst testen? Hier ist der vollständige Code:
Und die Ergebnisse:
LINQ vs. traditioneller Weg
Was LINQ betrifft, möchten Sie vielleicht Functional Programming (FP) lernen - nicht C # FP-Zeug, sondern echte FP-Sprache wie Haskell. In funktionalen Sprachen kann der Code auf bestimmte Weise ausgedrückt und dargestellt werden. In einigen Situationen ist es nicht-funktionalen Paradigmen überlegen.
FP ist dafür bekannt, dass es viel besser ist, Listen zu manipulieren ( Liste als Oberbegriff, unabhängig von
List<T>
). Angesichts dieser Tatsache ist die Möglichkeit, C # -Code in Bezug auf Listen funktionaler auszudrücken, eher eine gute Sache.Wenn Sie nicht überzeugt sind, vergleichen Sie die Lesbarkeit von Code, der sowohl funktional als auch nicht funktional in meiner vorherigen Antwort zu diesem Thema geschrieben wurde.
quelle
foreach
effizienter ist, alsfor
wenn die Disparität tatsächlich auf absichtlich gebrochenen Code zurückzuführen ist. Die Gründlichkeit der Antwort macht sich bemerkbar, aber es ist leicht einzusehen, wie ein gelegentlicher Beobachter zu den falschen Schlussfolgerungen kommen kann.Zwar gibt es bereits einige großartige Ausführungen zu den Unterschieden zwischen for und foreach. Es gibt einige grobe Falschdarstellungen der Rolle von LINQ.
LINQ-Syntax ist nicht nur syntaktischer Zucker, der eine funktionale Programmiernäherung an C # bietet. LINQ bietet funktionale Konstrukte mit allen Vorteilen für C #. In Kombination mit der Rückgabe von IEnumerable anstelle von IList bietet LINQ eine verzögerte Ausführung der Iteration. In der Regel konstruieren die Benutzer jetzt eine IList und geben sie von ihren Funktionen wie dieser zurück
Verwenden Sie stattdessen die Yield Return-Syntax , um eine verzögerte Aufzählung zu erstellen.
Jetzt wird die Aufzählung erst durchgeführt, wenn Sie sie in eine ToList aufnehmen oder darüber iterieren. Und es tritt nur bei Bedarf auf (hier ist eine Aufzählung von Fibbonaci, die kein Stapelüberlaufproblem hat)
Wenn Sie ein Foreach über die Fibonacci-Funktion durchführen, wird die Sequenz 46 zurückgegeben. Wenn Sie die 30. wollen, ist das alles, was berechnet wird
Was uns viel Spaß macht, ist die Unterstützung in der Sprache für Lambda-Ausdrücke (in Kombination mit den Konstrukten IQueryable und IQueryProvider ermöglicht dies die funktionale Komposition von Abfragen für eine Vielzahl von Datensätzen; der IQueryProvider ist für die Interpretation der übergebenen Daten verantwortlich Ausdrücke und Erstellen und Ausführen einer Abfrage unter Verwendung der nativen Konstrukte der Quelle). Ich werde hier nicht auf die Details eingehen, aber es gibt eine Reihe von Blog-Posts, die zeigen, wie man hier einen SQL-Abfrageanbieter erstellt
Zusammenfassend sollten Sie IEnumerable vor IList zurückgeben, wenn die Konsumenten Ihrer Funktion eine einfache Iteration durchführen. Nutzen Sie die Funktionen von LINQ, um die Ausführung komplexer Abfragen zu verschieben, bis sie benötigt werden.
quelle
Die Lesbarkeit liegt im Auge des Betrachters. Einige Leute könnten sagen
ist perfekt lesbar; andere könnten sagen, dass dies undurchsichtig ist und es vorziehen würde
als es klarer zu machen, was getan wird. Wir können Ihnen nicht sagen, was Sie besser lesen können. In dem Beispiel, das ich hier erstellt habe, können Sie jedoch möglicherweise einige meiner Vorurteile erkennen ...
quelle
Der Unterschied zwischen LINQ und besteht
foreach
in zwei verschiedenen Programmierstilen: imperativ und deklarativ.Imperativ: In diesem Stil sagen Sie dem Computer: "Tu das ... jetzt tu das ... jetzt tu das, jetzt tu das". Sie füttern es ein Programm Schritt für Schritt.
Deklarativ: In diesem Stil teilen Sie dem Computer mit, wie das Ergebnis aussehen soll, und lassen ihn herausfinden, wie er dahin kommt.
Ein klassisches Beispiel für diese beiden Stile ist das Vergleichen von Assemblycode (oder C) mit SQL. In der Montage erteilen Sie Anweisungen (wörtlich) nacheinander. In SQL legen Sie fest, wie Daten zusammengefügt werden und welches Ergebnis Sie aus diesen Daten erzielen möchten.
Ein netter Nebeneffekt der deklarativen Programmierung ist, dass sie tendenziell etwas höher ist. Auf diese Weise kann sich die Plattform unter Ihnen entwickeln, ohne dass Sie Ihren Code ändern müssen. Zum Beispiel:
Was passiert hier? Verwendet Distinct einen Kern? Zwei? Fünfzig? Wir wissen es nicht und es interessiert uns nicht. Die .NET-Entwickler könnten es jederzeit umschreiben, solange es denselben Zweck erfüllt, zu dem unser Code nach einem Code-Update auf magische Weise schneller werden könnte.
Das ist die Kraft der funktionalen Programmierung. Und der Grund, warum Sie diesen Code in Sprachen wie Clojure, F # und C # (geschrieben mit einer funktionalen Programmier-Denkweise) finden, ist oft 3x-10x kleiner als die zwingenden Gegenstücke.
Schließlich mag ich den deklarativen Stil, weil ich in C # die meiste Zeit Code schreiben kann, der die Daten nicht verändert. Im obigen Beispiel
Distinct()
ändert sich der Balken nicht, es wird eine neue Kopie der Daten zurückgegeben. Dies bedeutet, dass sich die Bar, von der auch immer sie stammt, nicht plötzlich ändert.Lernen Sie also, wie die anderen Plakate sagen, die funktionale Programmierung. Es wird dein Leben verändern. Und wenn Sie können, tun Sie dies in einer echten funktionalen Programmiersprache. Ich bevorzuge Clojure, aber auch F # und Haskell sind eine ausgezeichnete Wahl.
quelle
var foo = bar.Distinct()
ist im Wesentlichen einIEnumerator<T>
bis Sie anrufen.ToList()
oder.ToArray()
. Dies ist ein wichtiger Unterschied, denn wenn Sie sich dessen nicht bewusst sind, kann dies zu schwer verständlichen Fehlern führen.Können andere Entwickler im Team LINQ lesen?
Wenn nicht, dann benutze es nicht oder eins von zwei Dingen wird passieren:
A für jede Schleife ist perfekt, um eine Liste zu durchlaufen, aber wenn Sie das nicht tun, verwenden Sie keine.
quelle