2009-07-28 4 views
14

У меня есть два примера кода. Первый не компилируется, но второй делает.Ошибка смещения области видимости в C#

Пример кода 1(не компилируется)

public void MyMethod(){ 
    int i=10; 

    for(int x=10; x<10; x++) { 
     int i=10; // Point1: compiler reports error 
     var objX = new MyOtherClass(); 
    } 

    var objX = new OtherClassOfMine(); // Point2: compiler reports error 
} 

Я понимаю, почему компилятор выдает сообщение об ошибке в Point1. Но я не понимаю, почему он сообщает об ошибке на Point2. И если вы говорите, что это из-за организации внутри MSIL, то почему второй пример кода компилируется?

кода 2(компилирует)

public void MyMethod(){ 

    for(int x=10; x<10; x++) { 
     int i=10; 
     var objX = new MyOtherClass(); 
    } 

    for(int x=10; x<10; x++) { 
     int i=10; 
     var objX = new MyOtherClass(); 
    } 
} 

Если простые правила видимости переменных применяются в примере кода 2, то почему бы не те же правила применяются к кодовому образца 1?

ответ

39

Здесь есть два соответствующих правила.

Первое соответствующее правила является:

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

(И еще один ответ на этой странице взывает другое место в спецификации, где мы называем это снова.)

Это само по себе достаточно, чтобы сделать это незаконно, но на самом деле второе правило делает это незаконно ,

Второе соответствующее правило в C# является:

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

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

Теперь, когда мы знаем, что, давайте аннотировать код:

public void MyMethod() 
{ // 1 
    int i=10; // i1 
    { // 2 -- invisible brace 
     for(int x=10; x<10; x++) // x2 
     { // 3 
     int i=10; // i3 
     var objX = new MyOtherClass(); // objX3 
     } // 3 
    } // 2 
    var objX = new OtherClasOfMine(); // objX1 
} // 1 

У вас есть три "простых имен", I, X и objX. У вас есть пять переменных, которые я обозначил i1, x2, i3, objX3 и objX1.

Самый внешний блок, который содержит использование i и objX, является блоком 1. Поэтому в блоке 1 i и objX должны всегда ссылаться на одно и то же. Но они этого не делают. Иногда я ссылаюсь на i1, а иногда на i3. То же самое с objX.

x, однако, только когда-либо означает x2, в каждом блоке.

Кроме того, обе переменные «i» находятся в одном и том же локальном пространстве декларации переменных переменных, как и переменные «objX».

Таким образом, эта программа является ошибкой несколькими способами.

В вашей второй программе:

public void MyMethod() 
{ // 1 
    { // 2 -- invisible 
     for(int x=10; x<10; x++) // x2 
     { // 3 
     int i=10; // i3 
     var objX = new MyOtherClass(); // objX3 
     } //3 
    } // 2 
    { // 4 -- invisible 
     for(int x=10; x<10; x++) // x4 
     { // 5 
     int i=10; // i5 
     var objX = new MyOtherClass(); // objX5 
     } //5 
    } // 4 
} // 1 

Теперь у вас есть опять три простых имен, и шесть переменных.

Внешние блоки, которые сначала содержат использование простого имени x, являются блоками 2 и 4. В течение всего блока 2 x относится к x2. На протяжении блока 4 x относится к x4. Поэтому это законно. То же самое с i и objX - они используются в блоках 3 и 5 и означают разные вещи в каждом. Но нигде одно и то же простое имя не означает, что в одном блоке означают две разные вещи.

Теперь вы можете заметить, что с учетом всего блока 1, x используется как x2, так и x4. Но нет упоминания о x, который находится внутри блока 1, но НЕ также внутри другого блока. Поэтому мы не считаем несогласованное использование в блоке 1 релевантным.

Кроме того, никакие пространства объявлений не перекрываются незаконными способами.

Таким образом, это законно.

+0

Спасибо, что было очень описательно. –

+0

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

+3

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

6

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

Во втором примере два for петли имеет независимые, неперекрывающиеся области, так что вы можете повторно использовать i и objX во втором цикле. Это также причина, по которой вы можете повторно использовать x в качестве счетчика циклов. Очевидно, это было бы немым ограничением, если бы вам пришлось составлять разные имена для каждого цикла стиля for(i=1;i<10;++i) в функции.

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

+0

Вы должны прочитать сообщение Эрика Липперта «C++ и Pit Of Despair». Это поможет объяснить, почему команда разработчиков C# приняла решения, которые они сделали. Мне особенно нравится эта цитата: «Когда я спрошу вас, ребята, что« интуитивно очевидная »вещь должна сделать, значительная часть населения не согласна!» –

12

Из спецификации языка C# ...

Объем локальной переменной объявлена ​​ в локальной переменной декларации является блок, в котором происходит декларация. Это ошибка для ссылки на локальную переменную в текстовой позиции, которая предшествует локальной переменной-declarator локальной переменной. В пределах локальной области ошибка компиляции объявляет другую локальную переменную или константу с таким же именем .

В образце 1 кода как i, так и objX объявлены в объеме функции, поэтому никакая другая переменная в любом блоке внутри этой функции не может совместно использовать имя с ними. В образце кода 2 оба objX объявляются внутри циклов for, что означает, что они не нарушают правило не переопределять локальные переменные во внутренних областях из другого объявления.

-1

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

+0

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

Смежные вопросы