if(true)
{
    string var = "VAR";
}

string var = "New VAR!";

Это приведет к:

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

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

Почему C # не умеет различать эти две области? Разве первая область IF не должна быть полностью отделена от остальной части метода?

Я не могу вызвать var извне if, поэтому сообщение об ошибке неверно, потому что первая var не имеет отношения ко второй области.

66
user1144 12 Янв 2010 в 16:51
3
Фактическая ошибка компилятора: локальная переменная с именем «x» не может быть объявлена ​​в этой области видимости, поскольку она придала бы другое значение переменной «x», которая уже используется в «родительской или текущей» области для обозначения чего-то еще.
 – 
Kris Krause
12 Янв 2010 в 16:57
1
 – 
David Espart
12 Янв 2010 в 17:09
5
Я предполагаю, что вы используете C # 2.0, поскольку var - ключевое слово в C # 3.0.
 – 
John Saunders
12 Янв 2010 в 17:10
- похоже, вы попадаете в ловушку, предполагая, что C # намного больше похож на C ++, чем есть на самом деле. Старайтесь не рассуждать о том, что делает C ++, с тем, что делает C # - это опасная территория!
 – 
AAT
12 Янв 2010 в 17:48
9
Var не является зарезервированным ключевым словом C # 3.0. "var" означает только "неявно ввести эту локальную переменную", когда (1) она появляется как тип объявления локальной переменной (или с использованием блока decl, или foreach, или для и т. д.) И (2), когда нет тип с именем "var" уже в области видимости. C # никогда не добавлял зарезервированные ключевые слова, начиная с C # 1; все новые ключевые слова являются контекстными ключевыми словами. Они имеют значение только в качестве ключевых слов, когда используются в контексте, который придает им это значение; в противном случае они являются законными идентификаторами.
 – 
Eric Lippert
12 Янв 2010 в 20:03

3 ответа

Лучший ответ

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

Учтите, что если бы объявление var в родительской области было до оператора if, возник бы неразрешимый конфликт имен. Компилятор просто не различает следующие два случая. Анализ выполняется исключительно на основе области действия , а не порядка объявления / использования, как вы, кажется, ожидаете.

Теоретически приемлемый (но недействительный для C #):

if(true)
{
    string var = "VAR";
}

string var = "New VAR!";

И неприемлемое (поскольку это будет скрывать родительскую переменную):

string var = "New VAR!";

if(true)
{
    string var = "VAR";
}

Оба трактуются одинаково с точки зрения переменных и масштабов.

Итак, есть ли в этом сценарии реальная причина, по которой вы не можете просто дать одной из переменных другое имя? Я предполагаю (надеюсь), что ваши фактические переменные не называются var, поэтому я не вижу в этом проблемы. Если вы все еще намереваетесь повторно использовать одно и то же имя переменной, просто поместите их в одноуровневые области:

if(true)
{
    string var = "VAR";
}

{
    string var = "New VAR!";
}

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

46
Sarah Vessels 13 Янв 2010 в 23:10
Конечно, компилятор сможет различать две области видимости, внутренняя переменная скроет только внешнюю и сделает ее недоступной.
 – 
Mats Fredriksson
12 Янв 2010 в 16:58
3
Да точно - это запрещено просто потому, что это может привести к путанице / считается плохой дизайнерской практикой.
 – 
Noldorin
12 Янв 2010 в 17:01
Хорошо видно с точки зрения IL. Я почти уверен, что область видимости if {} не существует, поэтому это будет означать, что компилятор будет делать трюки для значения потенциальной путаницы, поэтому, если я исправлю, это не будет активно запрещаться, а пассивно. Нужная функция просто не реализована
 – 
Rune FS
12 Янв 2010 в 17:16
4
Кстати, в комментарии к удаленному посту вы сказали, что условный оператор создает область видимости. Конечно, нет. блок создает область видимости. Многочисленные операторы создают области - for, foreach, using, try / catch / finally и т. Д. Однако условное выражение не входит в их число; созданная область видимости создается блоком, а не условным.
 – 
Eric Lippert
12 Янв 2010 в 21:02
@Eric: Поверьте, вы указали что-то подобное. :) Вы, конечно, правы, хотя это довольно тонкий момент. Фактически это фигурные скобки, обозначающие блок, с заметными исключениями, которые вы указали, которые обозначают блок. Поправьте меня, если я ошибаюсь, но если операторы с однострочными действиями не содержат дочерних областей?
 – 
Noldorin
12 Янв 2010 в 21:33

Разве это не просто неправильно?

Нет, в этом нет ничего плохого. Это правильная реализация раздела 7.5.2.1 спецификации C # «Простые имена, неизменные значения в блоках».

В спецификации указано:


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


Почему C # не умеет различать эти две области?

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

Не должна ли первая область видимости IF быть полностью отделена от остальной части метода?

Нет, не должно. Область видимости (и пространство объявления локальной переменной), определенная оператором блока в результате условного оператора, лексически является частью внешнего блока, который определяет тело метода. Следовательно, правила о содержимом внешнего блока применяются к содержимому внутреннего блока.

Я не могу вызвать var извне if, поэтому сообщение об ошибке неверно, потому что первая var не имеет отношения ко второй области.

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

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

Например:

class C 
{
    int x;
    void M()
    { 
        int x = 123;
    }
}

Это совершенно законно; область действия внешнего x перекрывает область действия внутреннего x, но это не ошибка. Что такое ошибка:

class C 
{
    int x;
    void M()
    { 
        Console.WriteLine(x);
        if (whatever)
        {
            int x = 123;
        }
    }
}

Потому что теперь простое имя «x» означает две разные вещи внутри тела M - это означает «this.x» и локальную переменную «x». Разработчиков и сопровождающих кода сбивает с толку, когда одно и то же простое имя означает две совершенно разные вещи в одном блоке , так что это незаконно.

Мы действительно позволяем параллельным блокам содержать одно и то же простое имя, используемое двумя разными способами; это законно:

class C 
{
    int x;
    void M()
    { 
        if (whatever)
        {
            Console.WriteLine(x);
        }
        if (somethingelse)
        {
            int x = 123;
        }
    }
}

Потому что теперь единственный блок, который содержит два несовместимых использования x, - это внешний блок, и этот блок не прямо содержит какое-либо использование "x", только косвенно .

35
Eric Lippert 12 Янв 2010 в 21:04
@Eric: Среди прочего, мне кажется, что blogs.msdn.com/ericlippert/archive/2009/08/03/… здесь актуально.
 – 
Brian
13 Янв 2010 в 02:05
Хорошая точка зрения; хотя на самом деле вопрос о том, говорим ли мы о блоке как о пространстве объявления локальной переменной или о блоке как об области видимости локальной переменной, в значительной степени не имеет отношения к вопросу. Проблема кажется связана с пространством объявления, поскольку объявление помечено. Но на самом деле речь идет о scope - у нас есть две сущности, которые успешно ищутся по их неквалифицированному имени и привязаны к разным сущностям в одном блоке.
 – 
Eric Lippert
13 Янв 2010 в 02:50
3
Пример с полем и локальной переменной с тем же именем, к которым осуществляется доступ из одного и того же блока, вероятно, является самым ярким в отношении обоснования этого проектного решения.
 – 
Pavel Minaev
13 Янв 2010 в 05:25
Возможно, это только я, но общий тон вашего ответа кажется немного агрессивным.
 – 
Philip Wallace
29 Янв 2013 в 23:33
@PhilipWallace: Возможно, вам будет интересно прочитать мое эссе на эту тему: blogs.msdn.com/b/ericlippert/archive/2008/02/20/…
 – 
Eric Lippert
30 Янв 2013 в 09:30

Это справедливо для C ++, но является источником многих ошибок и бессонных ночей. Я думаю, что разработчики C # решили, что лучше выдавать предупреждение / ошибку, поскольку в подавляющем большинстве случаев это ошибка, а не то, что действительно нужно кодеру.

Здесь ' s интересное обсуждение того, из каких частей спецификации возникла эта ошибка.

ИЗМЕНИТЬ (несколько примеров) -----

В C ++ допустимо следующее (и на самом деле не имеет значения, находится ли внешнее объявление до или после внутренней области, просто это будет более интересно и подвержено ошибкам, если оно будет раньше).

void foo(int a)
{
    int count = 0;
    for(int i = 0; i < a; ++i)
    {
        int count *= i;
    }
    return count;
}

Теперь представьте, что функция на несколько строк длиннее, и ошибку можно легко не заметить. Компилятор никогда не жалуется (не в старые времена, не уверен в новых версиях C ++), а функция всегда возвращает 0.

Очевидно, что поведение является ошибкой, поэтому было бы хорошо, если бы программа c ++ - lint или компилятор указали на это. Если это не ошибка, ее легко обойти, просто переименовав внутреннюю переменную.

Чтобы добавить оскорбления к травме, я помню, что у GCC и VS6 были разные мнения о том, где принадлежит переменная счетчика в циклах for. Один сказал, что это принадлежит внешнему прицелу, а другой сказал, что нет. Немного раздражает работа с кроссплатформенным кодом. Позвольте мне привести вам еще один пример, чтобы подсчитать мои очереди.

for(int i = 0; i < 1000; ++i)
{
    if(array[i] > 100)
        break;
}

printf("The first very large value in the array exists at %d\n", i);

Этот код работал в VS6 IIRC, а не в GCC. Как бы то ни было, в C # исправлено несколько вещей, и это хорошо.

11
Mats Fredriksson 12 Янв 2010 в 17:16