Подтвердить что ты не робот

Проблема компилятора Weird С# с неоднозначностью имени переменной

Возьмем следующий код:

class Foo
{
   string bar;

   public void Method()
   {
      if (!String.IsNullOrEmpty(this.bar))
      {
         string bar = "Hello";
         Console.Write(bar);
      }
   }
}

Это скомпилируется, и все хорошо. Однако теперь удалим префикс this.:

class Foo
{
   string bar;

   public void Method()
   {
      if (!String.IsNullOrEmpty(bar)) // <-- Removed "this."
      {
         string bar = "Hello";
         Console.Write(bar);
      }
   }
}

В этом случае я получаю ошибку компилятора. Я согласен, что это ошибка, однако это место ошибки, которая меня смущает. Ошибка на линии:

string bar = "Hello";

С сообщением:

Локальная переменная с именем "bar" не может быть объявлена ​​в этой области, потому что это означало бы другое значение для "бара", которое уже используется в "родительская или текущая" область для обозначения чего-то еще

Из того, что я понимаю о компиляторе, объявление bar поднимается вверху метода Method(). Однако, если в этом случае строка:

if (!String.IsNullOrEmpty(bar))

Должно считаться неоднозначным, поскольку bar может быть ссылкой на поле экземпляра или на локальную переменную, еще не объявленную.

Мне кажется странным, что удаление this. может привести к ошибке компиляции в другой строке. Другими словами, объявление локальной переменной bar вполне допустимо, если до сих пор в области не были сделаны потенциально неоднозначные ссылки на bar (заметьте, если я прокомментирую if (!String.IsNullOrEmpty(bar)), тогда ошибка исчезнет).

Что все кажется довольно педантичным, так что ваш вопрос?:

Мой вопрос в том, почему компилятор допускает неоднозначную ссылку на переменную до ее объявления в области, но затем флага сама декларация является избыточной. Должна ли двусмысленная ссылка на bar в String.IsNullOrEmpty() быть более точным местом ошибки? В моем примере это, конечно, легко заметить, но когда я столкнулся с этой проблемой в дикой природе, ссылка была страница вверх и намного сложнее отслеживать.

4b9b3361

Ответ 1

Из того, что я понимаю о компиляторе, объявление бара поднимается вверху метода Method().

Нет, это не так.

Сообщение об ошибке здесь довольно точное:

Локальная переменная с именем "bar" не может быть объявлена ​​в этой области, потому что она будет иметь другое значение для "bar", которая уже используется в "родительской или текущей" области , чтобы обозначить что-то еще.

Часть функции С#, которая нарушается, - это раздел 7.6.2.1 (спецификации С# 4 и 5):

Инвариантное значение в блоках
Для каждого вхождения данного идентификатора в виде полного простого имени (без списка аргументов типа) в выражении или деклараторе в пределах локального пространства декларации переменных (§3.3), немедленно включающего это вхождение, каждое другое вхождение того же идентификатора, что и полное простое имя в выражении или деклараторе должно ссылаться на один и тот же объект. Это правило гарантирует, что значение имени всегда будет одинаковым в данном блоке, блоке переключателя, for-, foreach- или use-statement или анонимной функции.

И в аннотированной спецификации С# у этого есть полезная аннотация от Эрика Липперта:

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

Помимо всего прочего, мне кажется, что это просто полезно для ясности. Даже если вторая версия была разрешена, первая - более ясная ИМО. Компилятор гарантирует, что вы не напишите патологически нечеткий код, когда вы можете очень легко его исправить, чтобы быть очевидным, что вы имеете в виду.

Другими словами: действительно ли вы хотите написать вторую версию?

В частности:

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

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

Ответ 2

Второе определение бара не вытягивается на уровень метода, его область действия является блоком if. Например, это вполне допустимо.

class Foo
{
    private string bar;

    public void Method()
    {
        if (!String.IsNullOrEmpty(bar)) // <-- No more this.
        {
            string bar1 = "Hello";
            Console.Write(bar);
        }

        if (!String.IsNullOrEmpty(bar)) // <-- No more this.
        {
            string bar1 = "Hello";
            Console.Write(bar);
        }
    }
}

Причина в том, что bar уже не является двусмысленным, существует четкое разграничение имен между внешней и внутренней областью bar/bar1 - компилятор запрещает переопределять внешнюю область bar локальным определением.