Чому я можу оголосити дочірню змінну з тим самим іменем, що і змінну в батьківській області?


23

Нещодавно я написав код, коли ненавмисно повторно використав ім'я змінної як параметр дії, оголошеної в межах функції, яка вже має однойменну змінну. Наприклад:

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

Коли я помітив дублювання, я був здивований, побачивши, що код складено та працює бездоганно, що не поведінка, яку я б очікував, виходячи з того, що я знаю про область в C #. Деякі швидкі Googling з'явилися ТАК питання, які скаржаться, що подібний код робить помилку, як-от Lambda Scope Уточнення . (Я вставив цей зразок коду в свій IDE, щоб побачити, чи буде він працювати, просто для того, щоб переконатися, що він ідеально працює.) Крім того, коли я входжу в діалогове вікно Перейменування у Visual Studio, перший xвиділяється як конфлікт імен.

Чому цей код працює? Я використовую C # 8 з Visual Studio 2019.


1
Лямбда переміщується до методу класу, який генерується компілятором, і, таким чином, весь xпараметр цього методу виводиться з області застосування. Див sharplab для прикладу.
Лассе В. Карлсен

6
Тут, мабуть, варто зазначити, що це не буде компілюватися при націлюванні на C # 7.3, тому це, здається, є ексклюзивним для C # 8.
Джонатан Чейз

Код у зв'язаному питанні також добре поєднується з шарплабом . Це може бути нещодавня зміна.
Лассе В. Карлсен

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: Дякую!
Ерік Ліпперт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.