2015-12-16 3 views
9

Mit Visual Studio 2013 versuche ich, die in Eric Lippert Blogeintrag "Closing over the loop variable considered harmful" genannten Gotcha reproduzieren.Reproduzieren der "Close über die Variable einer Foreach" Gotcha

In den Projekteigenschaften habe ich "C# 3.0" als Sprachversion gewählt (Build> Erweitert ...). Außerdem habe ich ".NET Framework 3.5" als Zielframework ausgewählt, als ob ich denke, dass dies nicht notwendig sein sollte, da dies ausschließlich die Sprache betrifft.

Rennen seinen Code:

using System; 
using System.Collections.Generic; 

namespace Project1 
{ 
    class Class1 
    { 
     public static void Main(string[] args) 
     { 
      var values = new List<int>() { 100, 110, 120 }; 
      var funcs = new List<Func<int>>(); 
      foreach (var v in values) 
      { 
       funcs.Add(() => v); 
      } 
      foreach (var f in funcs) 
       Console.WriteLine(f()); 
     } 
    } 
} 

Erwartete Ausgabe:

 
120 
120 
120 

tatsächliche Ausgang:

 
100 
110 
120 

Wie Eric Lippert himself in "Is there a reason for C#'s reuse of the variable in a foreach?" beantwortet:

Die for-Schleife wird nicht geändert, und die Änderung wird nicht auf vorherige Versionen von C# zurück portiert. Sie sollten daher weiterhin vorsichtig sein, wenn Sie dieses Idiom verwenden.

Was mache ich falsch?

Antwort

9

Scotts Antwort ist korrekt, könnte aber einige zusätzliche Erläuterungen verwenden.

Das Problem hier ist, dass der Schalter "Sprachversion" nicht tut, was Sie denken, dass es tut. Dies ist meiner Meinung nach ein bisschen falsch, da es ziemlich irreführend ist. Der Schalter "Sprachversion" bedeutet nicht "verwende den alten Compiler"; Es ist kein Kompatibilitätsmodus.

Eher bedeutet es "verwende den aktuellen Compiler und erzeuge einen Fehler, wenn ich ein Feature verwende, das in der ausgewählten Version der Sprache nicht verfügbar war."

Der Grund für diese Umstellung ist, dass eine Person in einem Entwicklerteam eine neue Version des Compilers "ausprobieren" kann, um sicherzustellen, dass ihr Code noch funktioniert, aber bevor sie einchecken, dass sie nicht versehentlich verwendet wurde ein Sprachfeature, an dem sich die Compiler ihrer Kollegen gegenseitig ersticken. Wenn Sie also die Sprachversion auf 3.0 setzen, funktioniert "dynamic" nicht (weil es in C# 4.0 hinzugefügt wurde), aber es ist immer noch die Version des Compilers, die Sie installiert haben.

Wie Scott hervorhebt, müssen Sie, wenn Sie den alten Compiler verwenden möchten, tatsächlich eine Kopie des alten Compilers auf Ihrem Rechner finden und sie explizit verwenden.

Weitere Beispiele, was dieser Schalter tut und was nicht, finden Sie unter http://ericlippert.com/2013/04/04/what-does-the-langversion-switch-do/.

+0

Wann bricht Code tatsächlich ab? Ich schätze, wenn der Code auf einen Fehler im alten Compiler stützte, der dann behoben wird oder wenn der neue Compiler einen Fehler einführt. Gibt es andere Szenarien, in denen dies nützlich sein könnte? – xehpuk

+0

@xehpuk: Ich bin mir nicht sicher, ob ich deine Frage verstehe. Es ist sicherlich der Fall, dass Code, der sich auf behobene Compiler-Fehler stützte, unabhängig vom Wert des langversion-Schalters bricht. Das Compiler-Team ist sehr bemüht, die Rückwärtskompatibilität bei der Behebung von Fehlern beizubehalten, dies ist jedoch nicht immer möglich. –

+0

Sie haben Recht, es ist immer noch derselbe Compiler. Der Anwendungsfall besteht also im Grunde darin, dass ein Projektmitglied eine neuere Version des Compilers hat, die älteren aber nicht einfach verwenden kann (weil er z. B. eine neuere Version von VS verwendet, die aus irgendeinem Grund diese Option nicht hat). – xehpuk

8

Ich glaube, dass, selbst wenn Sie C# 3.0 für Ihre Sprache wählen, der Compiler immer noch das neue Verhalten tut, ich glaube nicht, dass Sie das alte Verhalten im neuen Compiler neu erstellen können. Das einzige, was die Sprache einstellt, ist die Einschränkung der Verwendung von Sprachfunktionen, die in der neueren Version der Sprache eingeführt wurden.

Sie müssen eine Kopie des alten Compilers (VS 2012 oder älter) finden, um das Verhalten zu erhalten. Als Eric sagte "die Änderung wird nicht zurück portiert" auf frühere Versionen von C#, meinte er, dass sie kein Update für VS2012 veröffentlichen werden, um das Verhalten in diesem Compiler zu ändern (Als der Beitrag, den du zitiertest, geschrieben wurde, war VS2013 gerade herausgekommen und VS2012 war immer noch weit verbreitet).

EDIT: Wenn Sie wirklich das Verhalten neu erstellen möchten, müssen Sie Ihr eigenes Handbuch für jede Schleife schreiben.

public static void Main(string[] args) 
{ 
    var values = new List<int>() { 100, 110, 120 }; 
    var funcs = new List<Func<int>>(); 

    using (var enumerator = values.GetEnumerator()) 
    { 
     int v; 
     while (enumerator.MoveNext()) 
     { 
      //int v; // In C# 5+ the variable is declared here. 
      v = enumerator.Current; 
      funcs.Add(() => v); 
     } 
    } 

    foreach (var f in funcs) 
     Console.WriteLine(f()); 
} 

Dies wird Ihre erwartete Ausgabe ausgeben.

+4

Sie müssten für ein Spielzeugbeispiel keine alte Version von VS installieren. Sie könnten dies tun, indem Sie csc manuell von C: \ Windows \ Microsoft.NET \ Framework \ v3.5 \ csc.exe ausführen. –

Verwandte Themen