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

Переменная область в блоках операторов

for (int i = 0; i < 10; i++)
{
    Foo();
}
int i = 10; // error, 'i' already exists

----------------------------------------    

for (int i = 0; i < 10; i++)
{
    Foo();
}
i = 10; // error, 'i' doesn't exist

По моему пониманию сферы применения, первый пример должен быть точным. Тот факт, что ни один из них не разрешен, кажется еще более странным. Разумеется, "i" - либо в сфере охвата, либо нет.

Есть ли что-то неочевидное в области видимости, которую я не понимаю, что означает, что компилятор действительно не может это решить? Или это всего лишь случай использования компилятора в nanny-state?

4b9b3361

Ответ 1

По моему пониманию сферы применения, первый пример должен быть точным.

Ваше понимание сферы в порядке. Это не ошибка определения области видимости. Это непоследовательное использование простой ошибки имени.

int я = 10;//ошибка, 'i' уже существует

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

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

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

int i = 10;
int i = 10;

Разумеется, "i" находится либо в области видимости, либо нет.

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

class C 
{
    int i;
    void M()
    {
        string i;

Совершенно законный. Внешний я находится в области видимости по всему M. Нет никаких проблем с объявлением локального i, который затеняет внешнюю область. Что было бы проблемой, если бы вы сказали

class C 
{
    int i;
    void M()
    {
        int x = i;
        foreach(char i in ...

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

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

Я не понимаю вопроса. Очевидно, что компилятор способен полностью анализировать программу; Если компилятор не смог решить смысл каждого использования i, то как он мог сообщить об ошибке? Компилятор полностью может определить, что вы использовали "i" для обозначения двух разных вещей в одно и то же локальное пространство объявления переменных и соответственно сообщает об ошибке.

Ответ 2

Это потому, что пространство декларации определяет i на уровне метода. Переменная i выходит за пределы области действия в конце цикла, но вы все еще не можете обновить i, потому что i уже был определен в этом методе.

Scope vs Application Space:

http://csharpfeeds.com/post/11730/Whats_The_Difference_Part_Two_Scope_vs_Declaration_Space_vs_Lifetime.aspx

Вы хотите взглянуть на ответ Эрика Липперта (который по умолчанию всегда прав относительно таких вопросов).

http://blogs.msdn.com/ericlippert/archive/2009/08/03/what-s-the-difference-part-two-scope-vs-declaration-space-vs-lifetime.aspx

Вот комментарий от eric на вышеупомянутом посту, который, я думаю, рассказывает о том, почему они сделали то, что они сделали:

Посмотрите на это так. Он всегда должен быть законным для переноса декларации переменная UP в исходном коде так долго как вы держите его в одном блоке, правильно? Если бы мы сделали это так, как вы предположим, тогда это иногда юридические, а иногда и незаконные! Но то, чего мы действительно хотим избежать, что происходит в С++ - на С++, иногда перемещение переменной декларация фактически меняет привязки других простых имен!

Ответ 3

Из спецификация С# в объявлениях локальных переменных:

Объем объявленной локальной переменной в декларации с локальной переменной блок, в котором происходит объявление.

Теперь, конечно, вы не можете использовать i до его объявления, но область объявления i - это весь блок, который содержит его:

{
    // scope starts here
    for (int i = 0; i < 10; i++)
    {
        Foo();
    }
    int i = 10;
}

Переменная for i находится в дочерней области, следовательно, столкновение имен переменных.

Если мы изменим положение объявления, столкновение станет более ясным:

{
    int i = 10;

    // collision with i
    for (int i = 0; i < 10; i++)
    {
        Foo();
    }
}

Ответ 4

В первом примере объявление я за пределами цикла делает я локальной переменной функции. В результате это ошибка, когда в любом блоке этой функции объявляется другое имя переменной i.

Во-вторых, я находится в области видимости только во время цикла. Вне цикла я больше не могу получить доступ.

Итак, вы видели ошибки, но нет ничего плохого в этом.

for (int i = 0; i < 10; i++)
{
  // do something
}

foreach (Foo foo in foos)
{
   int i = 42;
   // do something 
}

Поскольку область я ограничена в каждом блоке.

Ответ 5

Да, я второй комментарий компилятора "nanny-state". Интересно, что это нормально.

for (int i = 0; i < 10; i++)
{

}

for (int i = 0; i < 10; i++)
{

}

и это нормально

for (int i = 0; i < 10; i++)
{

}

for (int j = 0; j < 10; j++)
{
    var i = 12;                
}

но это не

for (int i = 0; i < 10; i++)
{
    var x = 2;
}

var x = 5;

хотя вы можете это сделать

for (int i = 0; i < 10; i++)
{
    var k = 12;
}

for (int i = 0; i < 10; i++)
{
    var k = 13;
}

Все немного непоследовательно.

ИЗМЕНИТЬ

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

перед

/*
 * doing two different things with the same name is unclear
 */
for (var index = 0; index < people.Count; index++)
{
    people[index].Email = null;
}
var index = GetIndexForSomethingElse(); 

после

/*
 * Now there is only one meaning for index in this scope
 */
ClearEmailAddressesFor(people); // the method name works like a comment now
var index = GetIndexForSomethingElse();

/*
 * Now index has a single meaning in the scope of this method.
 */
private void ClearEmailAddressesFor(IList<Person> people)
{
    for (var index = 0; index < people.Count; index++)
    {
        people[index].Email = null;
    }
}

Ответ 6

Или просто случай nanny-state compilerism?

Именно это. Нет смысла в "повторном использовании" имен переменных в том же методе. Это просто источник ошибок и ничего более.

Ответ 7

Мне кажется, что компилятор означает, что i был объявлен на уровне метода и помечен в цикле for.

Итак, в случае 1 - вы получаете ошибку, что переменная уже существует, что она делает

& в случае 2 - поскольку переменная ограничена только в цикле for, к ней нельзя получить доступ за пределами этого цикла

Чтобы этого избежать, вы можете:

var i = 0;

for(i = 0, i < 10, i++){
}

i = 10;

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

НТН

Ответ 8

вам нужно сделать

            int i ;
            for ( i = 0; i < 10; i++)
            {

            }
            i = 10;

Ответ 9

class Test
{
    int i;
    static int si=9; 

    public Test()
    {
        i = 199;
    }

    static void main()
    {
        for (int i = 0; i < 10; i++)
        {
            var x = 2;
        }

        { var x = 3; }

        {    // remove outer "{ }" will generate compile error
            int si = 3; int i = 0;

             Console.WriteLine(si);
             Console.WriteLine(Test.si);
             Console.WriteLine(i);
             Console.WriteLine((new Test()).i);
        }
    }
}