2010-07-13 4 views
9

Ist es besser, eine Variable, die in einer Schleife außerhalb der Schleife verwendet wird, lieber als im Inneren zu deklarieren? Manchmal sehe ich Beispiele, in denen eine Variable innerhalb der Schleife deklariert wird. Führt dies dazu, dass das Programm bei jeder Ausführung der Schleife Speicher für eine neue Variable reserviert? Oder ist .NET intelligent genug, um zu wissen, dass es sich wirklich um dieselbe Variable handelt.Sollen Variablenangaben immer außerhalb einer Schleife stehen?

Zum Beispiel den Code unten von this answer.

public static void CopyStream(Stream input, Stream output) 
{ 
    byte[] buffer = new byte[32768]; 
    while (true) 
    { 
     int read = input.Read (buffer, 0, buffer.Length); 
     if (read <= 0) 
      return; 
     output.Write (buffer, 0, read); 
    } 
} 

Wäre diese modifizierte Version effizienter?

public static void CopyStream(Stream input, Stream output) 
{ 
    int read; //OUTSIDE LOOP 
    byte[] buffer = new byte[32768]; 
    while (true) 
    { 
     read = input.Read (buffer, 0, buffer.Length); 
     if (read <= 0) 
      return; 
     output.Write (buffer, 0, read); 
    } 
} 

Antwort

9

Nein, es wäre nicht effizienter. Allerdings würde ich es auf diese Weise neu zu schreiben, die sie außerhalb der Schleife ohnehin zu erklären passiert:

byte[] buffer = new byte[32768]; 
int read; 
while ((read = input.Read(buffer, 0, buffer.Length)) > 0) 
{ 
    output.Write(buffer, 0, read); 
} 

Ich bin nicht generell ein Fan von Nebenwirkungen unter Bedingungen verwendet wird, aber effektiv die Read Methode gibt Ihnen zwei Datenbits: ob Sie das Ende des Streams erreicht haben oder nicht und wie viel Sie gelesen haben. Die while-Schleife sagt jetzt: "Während wir es geschafft haben, einige Daten zu lesen ... kopieren Sie sie."

Es ist ein bisschen wie int.TryParse mit:

if (int.TryParse(text, out value)) 
{ 
    // Use value 
} 

Wieder sind Sie in der Bedingung eine Nebenwirkung der Aufruf der Methode. Wie ich sage, mache ich es mir nicht zur Gewohnheit, dieses außer für dieses bestimmte Muster zu tun, wenn Sie mit einer Methode arbeiten, die zwei Datenbits zurückgibt.

Die gleiche Sache kommt Zeilen aus einem TextReader Lesen:

string line; 
while ((line = reader.ReadLine()) != null) 
{ 
    ... 
} 

auf Ihre ursprüngliche Frage gehen: Wenn eine Variable in jeder Iteration einer Schleife initialisiert werden wird und es nur benutzt, um Innerhalb des Körpers der Schleife würde ich es fast immer innerhalb der Schleife erklären. Eine kleine Ausnahme hier ist, wenn die Variable von einer anonymen Funktion erfasst wird - an diesem Punkt wird es einen Unterschied im Verhalten machen, und ich würde wählen, welche Form mir das gewünschte Verhalten gab ... aber das ist fast immer das "Deklarieren nach innen "Form sowieso.

EDIT: Wenn es um Scoping geht, verlässt der Code oben in der Tat die Variable in einem größeren Umfang als es sein muss ... aber ich glaube, es macht die Schleife klarer. Sie können dies immer durch das Angebot einen neuen Bereich einzuführen, wenn Sie kümmern zu:

{ 
    int read; 
    while (...) 
    { 
    } 
} 
+1

ich mit dem Umfang Ausgabe zustimmen. Wenn jemand alten Code (oder Code von jemand anderem) lesen muss, ist es nett, eine Variable mit dem richtigen Umfang zu haben. Wenn ich den Bereich verlasse, muss ich mich nicht mehr darum kümmern, was der letzte Wert dieser Variablen sein könnte. – Torlack

+0

@Torlack: Ich werde bearbeiten, um dies zu adressieren. –

1

Ich habe die letztere als eine Frage der persönlichen Gewohnheit im Allgemeinen bevorzugt, weil, auch wenn .NET intelligent genug ist, andere Umgebungen, in denen ich könnte später funktionieren möglicherweise nicht schlau genug. Es könnte nichts anderes sein als das Kompilieren einer zusätzlichen Codezeile innerhalb der Schleife, um die Variable erneut zu initialisieren, aber es ist immer noch Overhead.

Auch wenn sie für alle messbaren Zwecke in einem gegebenen Beispiel identisch sind, würde ich sagen, dass letzteres weniger die Chance hat, auf lange Sicht Probleme zu verursachen.

+2

Ich stimme nicht zu - weil Sie jetzt eine Variable mit einem größeren Umfang haben, als sie benötigt, was in der Regel schlecht für die Lesbarkeit ist. Persönlich denke ich, dass Sie sich an die Idiome der Umgebung anpassen sollten, in der Sie arbeiten - wenn Sie versuchen, C++, Java und C# auf die gleiche Weise zu codieren, werden Sie größere Probleme haben. –

+0

Fair genug, und ich stimme definitiv zu, dass man die Werkzeuge richtig verwenden sollte, anstatt sie zu zwingen, gleich zu sein. Ich würde sicherlich nicht anders meinen wollen. Ich denke, in diesem speziellen Beispiel ist der Umfang keine große Sache, weil er sowieso sofort danach endet. Für den Kontext des gesamten Codes gibt es definitiv eine Menge zu sagen, denn es gibt sehr selten eine globale Lösung für alle Fälle ähnlicher Probleme. – David

1

Es ist wirklich egal, und wenn ich den Code für dieses bestimmte Beispiel überprüfen würde, wäre mir egal egal.

Beachten Sie jedoch, dass die beiden sehr unterschiedliche Dinge bedeuten können, wenn Sie die 'read'-Variable in einem Abschluss erfassen.

Sehen Sie diesen hervorragenden Beitrag von Eric Lippert, wo dieses Problem foreach-Schleifen in Bezug auf aufgeht - http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

3

Im unwahrscheinlichen Umgebung, die Ihnen nicht mit diesem hilft, wäre es immer noch eine Mikro-Optimierung sein. Faktoren wie Klarheit und richtiges Scoping sind viel wichtiger als der Edge-Fall, in dem dies praktisch keinen Unterschied macht.

Sie sollten Ihren Variablen den richtigen Umfang geben, ohne über die Leistung nachzudenken. Natürlich sind komplexe Initialisierungen ein anderes Biest. Wenn also etwas nur einmal initialisiert werden soll, aber nur innerhalb einer Schleife verwendet wird, möchten Sie es dennoch außerhalb deklarieren.

+0

+1 Auch wenn die Variable bei jeder Iteration der Schleife zugewiesen wird, möchten Sie sie wahrscheinlich im Inneren deklarieren. Der Leistungsunterschied ist meistens vernachlässigbar, aber das Scoping ist es nicht. – helpermethod

+0

Ich stimme zu, ich sprach vor allem über Variablen, die innerhalb der Schleife verwendet, aber nicht geändert, sondern außerhalb der Schleife deklariert und initialisiert werden, weil die Initialisierung des Objekts nicht trivial ist.Wie Jon Skeet in seiner Antwort erwähnt hat, können Sie einen neuen Bereich einführen, um ihn außerhalb der Schleife zu halten, aber immer noch mit dem richtigen Scoping. Dies funktioniert sehr gut mit Dingen wie Resource-Handles, in diesem Fall können Sie einen neuen Bereich mit dem Konstrukt using (...) einführen. –

2

Ich werde den meisten dieser anderen Antworten mit einem Vorbehalt zustimmen.

Wenn Sie Lambda-Ausdrücke verwenden, müssen Sie beim Erfassen von Variablen vorsichtig sein.

static void Main(string[] args) 
{ 
    var a = Enumerable.Range(1, 3); 
    var b = a.GetEnumerator(); 
    int x; 
    while(b.MoveNext()) 
    { 
     x = b.Current; 
     Task.Factory.StartNew(() => Console.WriteLine(x)); 
    } 
    Console.ReadLine(); 
} 

geben das Ergebnis

3 
3 
3 

Wo

static void Main(string[] args) 
{ 
    var a = Enumerable.Range(1, 3); 
    var b = a.GetEnumerator(); 
    while(b.MoveNext()) 
    { 
     int x = b.Current; 
     Task.Factory.StartNew(() => Console.WriteLine(x)); 
    } 
    Console.ReadLine(); 
} 

wird das Ergebnis

1 
2 
3 

oder einige um dort geben. Dies ist der Fall, wenn die Aufgabe schließlich gestartet wird, überprüft sie den aktuellen Wert ihrer Referenz auf x. im ersten Beispiel zeigten alle 3 Schleifen auf die gleiche Referenz, während sie im zweiten Beispiel alle auf unterschiedliche Referenzen zeigten.

2

Wie bei vielen einfachen Optimierungen wie dieser, erledigt der Compiler das für Sie. Wenn Sie diese beiden versuchen und an der IL Versammlungen schauen in ildasm können Sie sehen, dass sie beide eine einzige int32 lesen Variable deklarieren, obwohl sie die Erklärungen nicht neu anordnen:

.locals init ([0] int32 read, 
      [1] uint8[] buffer, 
      [2] bool CS$4$0000) 

    .locals init ([0] uint8[] buffer, 
      [1] int32 read, 
      [2] bool CS$4$0000)