переменная область действия в блоках операторов - PullRequest
13 голосов
/ 22 апреля 2010
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

Насколько я понимаю, первый пример должен подойти.Тот факт, что ни один из них не разрешен, кажется еще более странным.Конечно, «я» либо в объеме или нет.

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

Ответы [ 9 ]

16 голосов
/ 22 апреля 2010

Насколько я понимаю, первый пример должен быть в порядке.

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

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

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

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

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

int i = 10;
int i = 10;

Конечно, «я» либо в области видимости, либо нет.

Конечно, но что с того? Находится ли данное i в области или нет, не имеет значения. Например:

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

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

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

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

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

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

10 голосов
/ 22 апреля 2010

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

Область действия и пространство декларации:

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

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

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

5 голосов
/ 22 апреля 2010

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

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

Теперь, конечно, вы не можете использовать 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();
    }
}
2 голосов
/ 22 апреля 2010

Да, я второй комментарий "компиляции няньки-государства". Что интересно, это нормально.

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;
}

Это все немного противоречиво.

EDIT

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

до

/*
 * 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;
    }
}
1 голос
/ 22 апреля 2010

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

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

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

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

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

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

0 голосов
/ 23 апреля 2010
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);
        }
    }
}
0 голосов
/ 22 апреля 2010

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

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

            }
            i = 10;
0 голосов
/ 22 апреля 2010

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

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

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

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

var i = 0;

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

i = 10;

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

HTH

0 голосов
/ 22 апреля 2010

Или это просто случай с няней compilerism

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

...