Warum kann ich eine untergeordnete Variable mit demselben Namen wie eine Variable im übergeordneten Bereich deklarieren?

23

Ich habe kürzlich einen Code geschrieben, in dem ich einen Variablennamen unbeabsichtigt als Parameter einer Aktion wiederverwendet habe, die in einer Funktion deklariert wurde, die bereits eine gleichnamige Variable enthält. Zum Beispiel:

var x = 1;
Action<int> myAction = (x) => { Console.WriteLine(x); };

Als ich die Duplizierung entdeckte, war ich überrascht zu sehen, dass der Code perfekt kompiliert und ausgeführt wurde. Dies ist kein Verhalten, das ich aufgrund meiner Kenntnisse über den Umfang in C # erwarten würde. Einige schnelle googeln drehte SO Fragen auf, die diesen ähnlichen Code beschweren sich einen Fehler, wie zum Beispiel produzieren Lambda Scope Klärung . (Ich habe diesen Beispielcode in meine IDE eingefügt, um zu prüfen, ob er ausgeführt wird, um sicherzugehen, dass er einwandfrei ausgeführt wird.) Wenn ich in Visual Studio das Dialogfeld "Umbenennen" aufrufe, wird der erste xals Namenskonflikt hervorgehoben.

Warum funktioniert dieser Code? Ich verwende C # 8 mit Visual Studio 2019.

stellr42
quelle
1
Das Lambda wird in eine Methode für eine Klasse verschoben, die vom Compiler generiert wird, und somit wird der gesamte xParameter dieser Methode aus dem Bereich verschoben. Ein Beispiel finden Sie unter sharplab .
Lasse V. Karlsen
6
Es ist wahrscheinlich erwähnenswert, dass dies nicht kompiliert wird, wenn auf C # 7.3 abgezielt wird. Dies scheint also exklusiv für C # 8 zu sein.
Jonathon Chase
Der Code in der verknüpften Frage lässt sich auch in Sharplab gut kompilieren . Dies könnte eine kürzlich erfolgte Änderung sein.
Lasse V. Karlsen
2
fand einen Betrüger (ohne Antwort): stackoverflow.com/questions/58639477/…
bolov

Antworten:

26

Warum funktioniert dieser Code? Ich verwende C # 8 mit Visual Studio 2019.

Sie haben Ihre eigene Frage beantwortet! Das liegt daran, dass Sie C # 8 verwenden.

Die Regel von C # 1 bis 7 lautete: Ein einfacher Name kann nicht verwendet werden, um zwei verschiedene Dinge im selben lokalen Bereich zu bedeuten. (Die eigentliche Regel war etwas komplexer als diese, beschreibt jedoch, wie langwierig sie ist. Einzelheiten finden Sie in der C # -Spezifikation.)

Die Absicht dieser Regel war es, die Art von Situation zu verhindern, von der Sie in Ihrem Beispiel sprechen, in der es sehr leicht wird, über die Bedeutung des Lokalen verwirrt zu werden. Diese Regel wurde insbesondere entwickelt, um Verwirrungen wie:

class C 
{
  int x;
  void M()
  {
    x = 123;
    if (whatever)
    {
      int x = 356;
      ...

Und jetzt haben wir eine Situation, in der im Körper von M, xsowohl this.xals auch lokal bedeutet x.

Obwohl gut gemeint, gab es eine Reihe von Problemen mit dieser Regel:

  • Es wurde nicht nach Spezifikation implementiert. Es gab Situationen, in denen ein einfacher Name beispielsweise sowohl als Typ als auch als Eigenschaft verwendet werden konnte, diese wurden jedoch nicht immer als Fehler gekennzeichnet, da die Fehlererkennungslogik fehlerhaft war. (Siehe unten)
  • Die Fehlermeldungen waren verwirrend formuliert und uneinheitlich gemeldet. Für diese Situation gab es mehrere verschiedene Fehlermeldungen. Sie identifizierten den Täter uneinheitlich; das heißt, manchmal wurde der innere Gebrauch gerufen, manchmal der äußere , und manchmal war es nur verwirrend.

Ich bemühte mich beim Umschreiben von Roslyn, dies zu klären. Ich habe einige neue Fehlermeldungen hinzugefügt und die alten konsistent gemacht, um festzustellen, wo der Fehler gemeldet wurde. Dieser Aufwand war jedoch zu gering, zu spät.

Das C # -Team entschied für C # 8, dass die gesamte Regel mehr Verwirrung stiftete als verhinderte, und die Regel wurde aus der Sprache entfernt. (Vielen Dank an Jonathon Chase für die Feststellung, wann der Ruhestand eingetreten ist.)

Wenn Sie daran interessiert sind, die Geschichte dieses Problems zu erfahren und zu erfahren, wie ich versucht habe, es zu beheben, lesen Sie die folgenden Artikel, die ich darüber geschrieben habe:

https://ericlippert.com/2009/11/02/simple-names-are-not-so-simple/

https://ericlippert.com/2009/11/05/simple-names-are-not-so-simple-part-two/

https://ericlippert.com/2014/09/25/confusing-errors-for-a-confusing-feature-part-one/

https://ericlippert.com/2014/09/29/confusing-errors-for-a-confusing-feature-part-two/

https://ericlippert.com/2014/10/03/confusing-errors-for-a-confusing-feature-part-three/

Am Ende des dritten Teils stellte ich fest, dass es auch eine Wechselwirkung zwischen dieser Funktion und der Funktion "Farbe Farbe" gab - dh der Funktion, die Folgendes ermöglicht:

class C
{
  Color Color { get; set; }
  void M()
  {
    Color = Color.Red;
  }
}

Hier haben wir den einfachen Namen verwendet Color, um sowohl auf this.Colorden aufgezählten Typ als auch auf den aufgezählten Typ zu verweisen Color. Nach einer strengen Lektüre der Spezifikation sollte dies ein Fehler sein, aber in diesem Fall war die Spezifikation falsch und die Absicht war, dies zuzulassen, da dieser Code eindeutig ist und es ärgerlich wäre, den Entwickler dazu zu bringen, ihn zu ändern.

Ich habe diesen Artikel nie geschrieben, in dem all die seltsamen Wechselwirkungen zwischen diesen beiden Regeln beschrieben werden, und es wäre ein bisschen sinnlos, dies jetzt zu tun!

Eric Lippert
quelle
Der Code in der Frage kann nicht für C # 6, 7, 7.1, 7.2 und 7.3 kompiliert werden. Dies ergibt "CS0136: Ein lokaler oder Parameter mit dem Namen 'x' kann in diesem Bereich nicht deklariert werden, da dieser Name ...". Es scheint, als ob die Regel bis C # 8 noch durchgesetzt wird.
Jonathon Chase
@ JonathonChase: Danke!
Eric Lippert