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