Почему я могу объявить дочернюю переменную с тем же именем, что и переменная в родительской области?

Недавно я написал код, в котором случайно повторно использовал имя переменной в качестве параметра действия, объявленного в функции, в которой уже есть переменная с таким же именем. Например:

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

Когда я заметил дублирование, я был удивлен, увидев, что код скомпилирован и работает отлично, чего я не ожидал, исходя из того, что я знаю о области видимости в C #. Некоторый быстрый поиск в Google обнаружил SO-вопросы, которые жалуются, что аналогичный код действительно вызывает ошибку, например Пояснение к области лямбда-выражения. (Я вставил этот образец кода в свою среду IDE, чтобы проверить, запустится ли он, на всякий случай; он работает отлично.) Кроме того, когда я вхожу в диалоговое окно «Переименовать» в Visual Studio, первый x выделяется как конфликт имен.

Почему этот код работает? Я использую C # 8 с Visual Studio 2019.


person stellr42    schedule 29.01.2020    source источник
comment
Лямбда перемещается в метод класса, который создается компилятором, и, таким образом, весь параметр x этого метода перемещается за пределы области видимости. См sharplab для примера.   -  person Lasse V. Karlsen    schedule 29.01.2020
comment
Здесь, вероятно, стоит отметить, что это не будет компилироваться при ориентации на C # 7.3, поэтому, похоже, это эксклюзивно для C # 8.   -  person Jonathon Chase    schedule 29.01.2020
comment
Код в связанном вопросе также компилирует штраф в sharplab. Это могло быть недавнее изменение.   -  person Lasse V. Karlsen    schedule 29.01.2020
comment
обнаружил обман (без ответа): stackoverflow.com/questions/58639477/   -  person bolov    schedule 29.01.2020


Ответы (1)


Почему этот код работает? Я использую C # 8 с Visual Studio 2019.

Вы ответили на свой вопрос! Это потому, что вы используете C # 8.

Правило от C # 1 до 7 гласило: простое имя не может использоваться для обозначения двух разных вещей в одной и той же локальной области. (Фактическое правило было немного более сложным, но описывать его утомительно; подробности см. В спецификации C #.)

Цель этого правила состояла в том, чтобы предотвратить ситуацию, о которой вы говорите в своем примере, когда становится очень легко запутаться в значении локального. В частности, это правило было разработано для предотвращения таких недоразумений, как:

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

И теперь у нас есть ситуация, когда внутри тела M, x означает как this.x, так и локальный x.

Хотя это правило было сделано из лучших побуждений, у этого правила был ряд проблем:

  • Это не было реализовано в соответствии со спецификацией. Были ситуации, когда простое имя могло использоваться, скажем, как тип и свойство, но они не всегда помечались как ошибки, потому что логика обнаружения ошибок была некорректной. (См. ниже)
  • Сообщения об ошибках были сформулированы непоследовательно и непоследовательно. Для этой ситуации было несколько разных сообщений об ошибках. Они непоследовательно идентифицировали преступника; то есть иногда вызывается внутреннее использование, иногда внешнее, а иногда это просто сбивает с толку.

Я попытался переписать Roslyn, чтобы разобраться с этим; Я добавил несколько новых сообщений об ошибках и согласовал старые в отношении того, где была обнаружена ошибка. Однако этого было слишком мало, слишком поздно.

Команда разработчиков C # решила, что для C # 8 это правило вызывает больше путаницы, чем предотвращает, и это правило было удалено из языка. (Спасибо Джонатону Чейзу за определение даты выхода на пенсию.)

Если вам интересно узнать историю этой проблемы и то, как я пытался ее исправить, посмотрите эти статьи, которые я написал об этом:

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/

В конце третьей части я отметил, что эта функция также взаимодействует с функцией «Цветной цвет», то есть с функцией, которая позволяет:

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

Здесь мы использовали простое имя Color для обозначения как this.Color, так и перечислимого типа Color; согласно строгому прочтению спецификации это должно быть ошибкой, но в данном случае спецификация была неправильной, и намерение состояло в том, чтобы разрешить это, поскольку этот код недвусмысленен и было бы неприятно заставить разработчика изменить его.

Я никогда не писал эту статью, описывающую все странные взаимодействия между этими двумя правилами, и было бы бессмысленно делать это сейчас!

person Eric Lippert    schedule 29.01.2020
comment
Код в вопросе не может быть скомпилирован для C # 6, 7, 7.1, 7.2 и 7.3, что дает CS0136: локальный параметр или параметр с именем 'x' не может быть объявлен в этой области, потому что это имя .... Это похоже на правило как по-прежнему применяется до C # 8. - person Jonathon Chase; 29.01.2020
comment
@JonathonChase: Спасибо! - person Eric Lippert; 29.01.2020