Dlaczego mogę zadeklarować zmienną podrzędną o takiej samej nazwie jak zmienna w zakresie nadrzędnym?

23

Niedawno napisałem jakiś kod, w którym nieumyślnie ponownie użyłem nazwy zmiennej jako parametru akcji zadeklarowanej w funkcji, która już ma zmienną o tej samej nazwie. Na przykład:

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

Kiedy zauważyłem duplikację, zdziwiłem się, widząc, że kod został skompilowany i działał idealnie, czego nie oczekiwałbym na podstawie tego, co wiem o zakresie w języku C #. Kilka szybkich googlingów pojawiło się w pytaniach SO, narzekających, że podobny kod powoduje błąd, takich jak wyjaśnienie zakresu lambda . (Wkleiłem ten przykładowy kod do mojego IDE, aby sprawdzić, czy będzie on działał, aby się upewnić; działa idealnie). Dodatkowo, kiedy wchodzę do okna dialogowego Zmień nazwę w Visual Studio, pierwszy xjest podświetlany jako konflikt nazw.

Dlaczego ten kod działa? Używam C # 8 z Visual Studio 2019.

stellr42
źródło
1
Lambda jest przenoszona do metody w klasie generowanej przez kompilator, a zatem cały xparametr tej metody zostaje usunięty z zakresu. Zobacz na przykład sharplab .
Lasse V. Karlsen
6
Warto tutaj zauważyć, że nie będzie się to kompilować podczas celowania w C # 7.3, więc wydaje się, że dotyczy to wyłącznie C # 8.
Jonathon Chase
Kod w połączonym pytaniu również dobrze komponuje się w sharplab . To może być ostatnia zmiana.
Lasse V. Karlsen
2
znalazłem dupe (bez odpowiedzi): stackoverflow.com/questions/58639477/...
bolov

Odpowiedzi:

26

Dlaczego ten kod działa? Używam C # 8 z Visual Studio 2019.

Odpowiedziałeś na swoje pytanie! To dlatego, że używasz C # 8.

Reguła od C # 1 do 7 brzmiała: prosta nazwa nie może oznaczać dwóch różnych rzeczy w tym samym zasięgu lokalnym. (Rzeczywista reguła była nieco bardziej skomplikowana, ale opisywała, jak to jest nużące; szczegółowe informacje można znaleźć w specyfikacji C #).

Celem tej reguły było zapobieżenie sytuacji, o której mówisz w swoim przykładzie, w której bardzo łatwo jest pomylić się co do znaczenia tego, co lokalne. W szczególności zasada ta została zaprojektowana w celu zapobiegania nieporozumieniom, takim jak:

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

A teraz mamy sytuację, w której wnętrze ciała oznacza zarówno lokalne M, xjak this.xi lokalne x.

Mimo dobrych intencji z tą regułą było wiele problemów:

  • Nie został zaimplementowany zgodnie ze specyfikacją. Zdarzały się sytuacje, w których można było użyć prostej nazwy jako, powiedzmy, zarówno typu, jak i właściwości, ale nie zawsze były one oznaczane jako błędy, ponieważ logika wykrywania błędów była wadliwa. (Patrz poniżej)
  • Komunikaty o błędach były myląco sformułowane i niespójnie zgłaszane. W tej sytuacji było wiele różnych komunikatów o błędach. Niespójnie zidentyfikowali sprawcę; to znaczy, czasami wewnętrzne użycie byłoby odwołane, czasami zewnętrzne , a czasami było to po prostu mylące.

W przepisie Roslyn starałem się to rozwiązać; Dodałem kilka nowych komunikatów o błędach i sprawiłem, że stare były spójne pod względem miejsca zgłoszenia błędu. Jednak wysiłek ten był zbyt mały, zbyt późny.

Zespół C # zdecydował dla C # 8, że cała reguła powoduje więcej zamieszania niż zapobiega, i reguła została wycofana z języka. (Dzięki Jonathon Chase za ustalenie, kiedy nastąpiła emerytura.)

Jeśli chcesz poznać historię tego problemu i sposób, w jaki próbowałem go naprawić, zapoznaj się z artykułami, które o nim napisałem:

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/

Pod koniec trzeciej części zauważyłem, że istnieje także interakcja między tą funkcją a funkcją „Kolor koloru” - to znaczy funkcją, która pozwala:

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

W tym przypadku użyliśmy prostej nazwy Colorw odniesieniu zarówno do, jak this.Colori do wyliczonego typu Color; według ścisłego odczytania specyfikacji powinien to być błąd, ale w tym przypadku specyfikacja była niepoprawna, a intencją było zezwolenie na to, ponieważ kod ten jest jednoznaczny i denerwujące byłoby zmuszenie dewelopera do jego zmiany.

Nigdy nie napisałem tego artykułu opisującego wszystkie dziwne interakcje między tymi dwiema regułami, i byłoby to teraz bezcelowe!

Eric Lippert
źródło
Kod w pytaniu nie można skompilować dla C # 6, 7, 7.1, 7.2 i 7.3, co daje „CS0136: Lokalny lub parametr o nazwie„ x ”nie może być zadeklarowany w tym zakresie, ponieważ ta nazwa…”. Wygląda na to, że reguła jest nadal egzekwowana do C # 8
Jonathon Chase
@JonathonChase: Dzięki!
Eric Lippert