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

23

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

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

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

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

stellr42
источник
1
Лямбда перемещается в метод класса, который генерируется компилятором, и, таким образом, весь xпараметр этого метода выходит из области видимости. Смотрите sharplab для примера.
Лассе В. Карлсен
6
Здесь, вероятно, стоит отметить, что это не скомпилируется при нацеливании на C # 7.3, так что это, похоже, исключительно для C # 8.
Джонатон Чейз
Код в связанном вопросе также отлично компилируется в sharplab . Это может быть недавнее изменение.
Лассе В. Карлсен
2
нашел дурака (без ответа): stackoverflow.com/questions/58639477/…
болов

Ответы:

26

Почему этот код работает? Я использую 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; в соответствии со строгим прочтением спецификации, это должно быть ошибкой, но в этом случае спецификация была неправильной, и было намерение разрешить ее, поскольку этот код недвусмыслен, и было бы неприятно заставить разработчика изменить его.

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

Эрик Липперт
источник
Код в вопросе не компилируется для C # 6, 7, 7.1, 7.2 и 7.3, давая «CS0136: локальный или параметр с именем« x »не может быть объявлен в этой области, потому что это имя ...». Кажется, что это правило все еще применяется до C # 8.
Джонатон Чейз
@JonathonChase: Спасибо!
Эрик Липперт