Gibt es einen Unterschied zwischen diesen beiden Codeversionen?
foreach (var thing in things)
{
int i = thing.number;
// code using 'i'
// pay no attention to the uselessness of 'i'
}
int i;
foreach (var thing in things)
{
i = thing.number;
// code using 'i'
}
Oder ist es dem Compiler egal? Wenn ich von Unterschieden spreche, meine ich in Bezug auf Leistung und Speichernutzung. ..Oder im Grunde nur ein Unterschied oder sind die beiden nach der Kompilierung der gleiche Code?
c#
performance
memory
Alternatex
quelle
quelle
Antworten:
TL; DR - das sind äquivalente Beispiele auf der IL-Ebene.
DotNetFiddle macht dies hübsch zu beantworten, da Sie die resultierende IL sehen können.
Ich habe eine etwas andere Variante Ihres Schleifenkonstrukts verwendet, um meine Tests zu beschleunigen. Ich benutzte:
Variante 1:
Variante 2:
In beiden Fällen wurde die kompilierte IL-Ausgabe gleich wiedergegeben.
Um Ihre Frage zu beantworten: Der Compiler optimiert die Deklaration der Variablen und macht die beiden Variationen gleich.
Nach meinem Verständnis verschiebt der .NET IL-Compiler alle Variablendeklarationen an den Anfang der Funktion, aber ich konnte keine gute Quelle finden, aus der eindeutig hervorgeht, dass 2 . In diesem Beispiel sehen Sie, dass sie mit dieser Anweisung nach oben verschoben wurden:
Wobei wir ein bisschen zu besessen sind, wenn wir Vergleiche anstellen ...
Fall A: Werden alle Variablen nach oben verschoben?
Um dies etwas näher zu untersuchen, habe ich die folgende Funktion getestet:
Der Unterschied besteht darin, dass wir basierend auf dem Vergleich entweder ein
int i
oder ein deklarierenstring j
. Wieder verschiebt der Compiler alle lokalen Variablen an den Anfang der Funktion 2 mit:Ich fand es interessant festzustellen, dass
int i
der Code, der ihn unterstützt, immer noch generiert wird , obwohl er in diesem Beispiel nicht deklariert wird.Fall B: Was ist mit
foreach
stattfor
?Es wurde darauf hingewiesen, dass sich
foreach
das anders verhält alsfor
und dass ich nicht das Gleiche überprüfe, nach dem gefragt wurde. Also habe ich diese beiden Codeabschnitte eingefügt, um die resultierende IL zu vergleichen.int
Deklaration außerhalb der Schleife:int
Deklaration innerhalb der Schleife:Die resultierende IL mit der
foreach
Schleife unterschied sich tatsächlich von der IL, die unter Verwendung derfor
Schleife erzeugt wurde. Insbesondere wurden der Init-Block und der Schleifenabschnitt geändert.Der
foreach
Ansatz erzeugte mehr lokale Variablen und erforderte einige zusätzliche Verzweigungen. Im Wesentlichen springt es beim ersten Mal zum Ende der Schleife, um die erste Iteration der Aufzählung zu erhalten, und springt dann fast zum Anfang der Schleife zurück, um den Schleifencode auszuführen. Es wird dann wie erwartet weiter durchlaufen.Abgesehen von den Verzweigungsunterschieden, die durch die Verwendung der Konstrukte
for
und verursacht wurdenforeach
, gab es keinen Unterschied in der IL, basierend darauf, wo dieint i
Deklaration platziert wurde. Wir sind also immer noch der Meinung, dass beide Ansätze gleichwertig sind.Fall C: Was ist mit verschiedenen Compilerversionen?
In einem Kommentar, der 1 hinterlassen wurde , gab es einen Link zu einer SO-Frage bezüglich einer Warnung über den variablen Zugriff mit foreach und die Verwendung von Closure . Der Teil, der mir bei dieser Frage wirklich aufgefallen ist, war, dass es möglicherweise Unterschiede in der Funktionsweise des .NET 4.5-Compilers gegenüber früheren Versionen des Compilers gab.
Und hier hat mich die DotNetFiddler-Site im Stich gelassen - alles, was sie zur Verfügung hatten, war .NET 4.5 und eine Version des Roslyn-Compilers. Also habe ich eine lokale Instanz von Visual Studio aufgerufen und angefangen, den Code zu testen. Um sicherzustellen, dass ich dieselben Dinge verglichen habe, habe ich lokal erstellten Code in .NET 4.5 mit dem DotNetFiddler-Code verglichen.
Der einzige Unterschied, den ich feststellte, war der lokale Init-Block und die Variablendeklaration. Der lokale Compiler war bei der Benennung der Variablen etwas spezifischer.
Aber mit diesem kleinen Unterschied war es so weit, so gut. Ich hatte eine äquivalente IL-Ausgabe zwischen dem DotNetFiddler-Compiler und dem, was meine lokale VS-Instanz produzierte.
Also habe ich das Projekt für .NET 4, .NET 3.5 und zum guten Teil für den .NET 3.5 Release-Modus neu erstellt.
In allen drei zusätzlichen Fällen war die generierte IL gleichwertig. Die anvisierte .NET-Version hatte keine Auswirkung auf die IL, die in diesen Beispielen generiert wurde.
Um dieses Abenteuer zusammenzufassen: Ich denke, wir können mit Sicherheit sagen, dass es dem Compiler egal ist, wo Sie den primitiven Typ deklarieren, und dass es bei beiden Deklarationsmethoden keine Auswirkungen auf den Speicher oder die Leistung gibt. Und das gilt unabhängig von der Verwendung einer
for
oderforeach
-Schleife.Ich überlegte, noch einen weiteren Fall auszuführen, der einen Verschluss innerhalb der
foreach
Schleife enthielt . Aber Sie hatten nach den Auswirkungen der Deklaration einer primitiven Typvariablen gefragt, und ich dachte, ich würde zu weit über das hinausgehen, worüber Sie interessiert waren. Die zuvor erwähnte SO-Frage hat eine großartige Antwort , die einen guten Überblick über die Schließungseffekte für jede Iterationsvariable bietet.1 Vielen Dank an Andy für die Bereitstellung des ursprünglichen Links zur SO-Frage zu Schließungen innerhalb von
foreach
Schleifen.2 Es ist erwähnenswert, dass die ECMA-335-Spezifikation dies mit Abschnitt I.12.3.2.2 'Lokale Variablen und Argumente' behandelt. Ich musste die resultierende IL sehen und dann den Abschnitt lesen, damit klar wurde, was los war. Vielen Dank an den Ratschenfreak, der im Chat darauf hingewiesen hat.
quelle
foreach
Schleife überprüft und auch die Zielversion von .NET überprüft.Je nachdem, welchen Compiler Sie verwenden (ich weiß nicht einmal, ob C # mehr als einen hat), wird Ihr Code optimiert, bevor er in ein Programm umgewandelt wird. Ein guter Compiler wird feststellen, dass Sie dieselbe Variable jedes Mal mit einem anderen Wert neu initialisieren und den Speicherplatz dafür effizient verwalten.
Wenn Sie dieselbe Variable jedes Mal mit einer Konstanten initialisieren würden, würde der Compiler sie ebenfalls vor der Schleife initialisieren und darauf verweisen.
Es hängt alles davon ab, wie gut Ihr Compiler geschrieben ist, aber was Codierungsstandards betrifft, sollten Variablen immer den geringstmöglichen Umfang haben. Innerhalb der Schleife zu deklarieren ist das, was mir immer beigebracht wurde.
quelle
Im ersten Fall deklarieren und initialisieren Sie nur die innere Schleife, sodass jedes Mal, wenn die Schleife eine Schleife erhält, "i" innerhalb der Schleife neu initialisiert wird. In der zweiten deklarieren Sie nur außerhalb der Schleife.
quelle