Змінна, оголошена в for-loop, є локальною змінною?


133

Я використовую C # досить тривалий час, але так і не зрозумів наступного:

 public static void Main()
 {
     for (int i = 0; i < 5; i++)
     {

     }

     int i = 4;  //cannot declare as 'i' is declared in child scope                
     int A = i;  //cannot assign as 'i' does not exist in this context
 }

То чому я не можу використовувати значення 'i' поза блоком for, якщо воно не дозволяє мені оголосити змінну з цим ім'ям?

Я вважав, що змінна ітератора, що використовується циклом for, є дійсною лише в її області застосування.


8
Оскільки зовнішня область блоку включає сферу циклу for
V4Vendetta

3
Я думаю, що це (а деякі посібники зі стилів коду цього вимагають, особливо для динамічних мов) - це те, що всі змінні, оголошені в області, можна було оголосити на початку цього діапазону, тобто ваша функція може бути переписанаint i, A; for(int i = 0; i < 5; i++){ } i=4; A=i
Кіт

2
@ V4Vendetta: Це навпаки. Внутрішній блок - це чорний ящик для батьківського блоку.
Себастьян Мах

4
Крім технічних причин, чому це неможливо, чому це (або варіант цього) коли-небудь було розумним робити ?! Він, очевидно, служить іншій меті, тому дайте йому іншу назву.
Дейв

Я взагалі цього не усвідомлював, але це здається цілком відвертим обмеженням.
alan2тут

Відповіді:


119

Причина, через яку вам не дозволяється визначати однакову змінну як у циклі for, так і поза циклом for, полягає в тому, що змінні у зовнішній області є дійсними у внутрішній області. Це означає, що у фор-циклі будуть дві змінні 'i', якщо це було дозволено.

Див .: Області MSDN

Конкретно:

Область локальної змінної, оголошеної в локальній змінній-декларації (Розділ 8.5.1), є блоком, в якому відбувається оголошення.

і

Область локальної змінної, оголошеної в for-ініціалізаторі оператора for (Розділ 8.8.3), є for-ініціалізатором, for-умовою, for-ітератором та вміщеним оператором for для оператора.

А також: локальні оголошення змінної (Розділ 8.5.1 специфікації C #)

Конкретно:

Область локальної змінної, оголошеної в локальній змінній-декларації, є блоком, в якому відбувається декларація. Помилка посилання на локальну змінну в текстовому положенні, що передує локальній змінній-декларатору локальної змінної.У межах локальної змінної - це помилка часу компіляції при оголошенні іншої локальної змінної або константи з тим самим іменем.

(Наголос мій.)

Що означає, що сфера дії iвсередині вашого for-loop - це цикл for. Тоді як сфера iзовнішньої частини вашої for-петлі є головним плюсом методу for-loop. Значить, у вас є два входження iв цикл, який є недійсним відповідно до вищезазначеного.

Причина, через яку вам не дозволяють, int A = i;це тому, що int iвона використовується лише для використання в forциклі. Таким чином, він більше не доступний поза forпетлею.

Як ви бачите, обидва ці питання є результатом обстеження; перший випуск ( int i = 4;) призведе до двох iзмінних у межах forциклу. В той час як int A = i;це призведе до доступу до змінної, яка виходить за межі сфери.

Що ви можете зробити замість цього - оголосити, що він iмає бути приписаний до всього методу, а потім використовувати його як у методі, так і в області for-loop. Це дозволить уникнути порушення будь-якого правила.

public static void Main()
{
    int i;

    for (i = 0; i < 5; i++)
    {

    }

    // 'i' is only declared in the method scope now, 
    // no longer in the child scope -> valid.
    i = 4;

    // 'i' is declared in the method's scope -> valid. 
    int A = i;
}

Редагувати :

Зрозуміло, що компілятор C # може бути змінений, щоб цей код міг зібратися цілком коректно. Адже це справедливо:

for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i);
}

for (int i = 5; i > 0; i--)
{
    Console.WriteLine(i);
}

Але чи справді було б корисно для вашої читабельності коду та ремонтопридатності, щоб мати можливість писати код, такий як:

public static void Main()
{
    int i = 4;

    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(i);
    }

    for (int i = 5; i > 0; i--)
    {
        Console.WriteLine(i);
    }

    Console.WriteLine(i);
}

Подумайте про потенційні помилки тут, чи останнє iдрукує 0 чи 4? Зараз це дуже невеликий приклад, той, який досить легко простежити і відстежувати, але, безумовно, набагато менш доцільний і читабельний, ніж оголосити зовнішнє iіншою назвою.

Примітка:

Зверніть увагу, що правила розміщення C # відрізняються від правил розміщення C ++ . В C ++ змінні знаходяться лише в області застосування, де вони оголошені до кінця блоку. Що зробить ваш код дійсною конструкцією в C ++.


Має сенс, я думаю, що це повинно помилитися на внутрішній iзмінній; це здається мені більш очевидним.
Джордж Дакетт

Але сфера застосування для команди та її {}? Або це означає його батьків {}?
Іван V

2
Добре, якщо я оголошу "A" ПІСЛЯ for для твердження, він не дійсний у циклі for, як це оголошено пізніше. Ось чому я не розумію, чому не можна використовувати те саме ім’я.
Іван V

9
Можливо, ця відповідь повинна для повноти вказати на те, що правила сфери застосування C # відрізняються від правил для C ++ у цьому випадку. У C ++ змінні знаходяться лише в області, де вони оголошені до кінця блоку (див. Msdn.microsoft.com/en-us/library/b7kfh662(v=vs.80).aspx ).
AAT

2
Зауважте, що Java використовує проміжний підхід між C ++ і C #: оскільки це приклад ОП, він би був дійсним у Java, але якщо зовнішнє iвизначення було переміщено до циклу for, внутрішнє iвизначення буде позначене як недійсне.
Nicola Musatti

29

Відповідь J.Kommer правильна: коротко, незаконне оголошення локальної змінної у просторі оголошення локальної змінної, що перекриває інший місцевий простір оголошення змінної який має однойменний локальний.

Існує додаткове правило C #, яке порушено і тут. Додаткове правило полягає в тому, що просте ім'я заборонено використовувати для позначення двох різних об'єктів всередині двох різних локальних проміжних оголошень, що перекриваються. Тож не тільки ваш приклад є незаконним, але і незаконним:

class C
{
    int x;
    void M()
    {
        int y = x;
        if(whatever)
        {
            int x = 123;

Тому що тепер просте ім'я "x" вживається всередині місцевого простору декларування змінної "y" для позначення двох різних речей - "this.x" та локального "x".

Див. Http://blogs.msdn.com/b/ericlippert/archive/tags/simple+names/ для отримання додаткового аналізу цих проблем.


2
Також цікаво відзначити, що ваш прикладний код складеться, коли ви внесете зміниint y = this.x;
Філ

4
@Phil: Правильно. this.xце не проста назва .
Ерік Ліпперт

13

Існує спосіб оголошення та використання iвсередині методу після циклу:

static void Main()
{
    for (int i = 0; i < 5; i++)
    {

    }

    {
        int i = 4;
        int A = i;
    }
}

Це можна зробити на Java (це може походити з C Я не впевнений). Звичайно, це трохи безладно заради змінної назви.


Так, це походить від C / C ++.
Бранко Димитріевич

1
Я цього раніше не знав. Акуратна мовна особливість!

@MathiasLykkegaardLorenzen так
Chris S

7

Якби ви оголосили , i перш ніж ваш forцикл, ви думаєте , він повинен залишатися дійсним , щоб оголосити його всередині циклу?

Ні, тому що тоді сфера застосування цих двох перекриється.

Щодо неможливості зробити int A=i;, то це просто тому, що iіснує лише в forциклі, як це слід робити.


7

Окрім відповіді Дж. Комера (+1 btw). Це є в стандарті для сфери NET:

блок Якщо ви оголошуєте змінну в рамках блокової конструкції, такої як твердження If, область застосування цієї змінної є лише до кінця блоку. Термін експлуатації - до завершення процедури.

Процедура Якщо ви оголошуєте змінну в рамках процедури, але поза будь-яким оператором If, область є до кінця функції Sub або End. Термін експлуатації змінної - до завершення процедур.

Таким чином, декларований int i в заголовку циклу for буде доступний лише під час блоку циклу, АЛЕ його життя триває до завершення Main()коду.


5

Найпростіший спосіб подумати над цим - перенести зовнішнє оголошення I вище петлі. Це повинно стати очевидним тоді.

Це так само і в будь-якому випадку, тому неможливо зробити.


4

Також правила C # багато разів не потрібні з точки зору суворого програмування, але вони є, щоб зберегти чистий і читабельний код.

Наприклад, вони могли б зробити так, що якщо ви визначите його після циклу, то це нормально, проте, хтось, хто читає ваш код і пропустив рядок визначення, може подумати, що це має відношення до змінної циклу.


2

Відповідь Комера технічно правильна. Дозвольте перефразувати це яскравою метафорою на сліпому екрані.

Існує односторонній сліпий екран між блоком for-блоком та зовнішнім зовнішнім блоком таким чином, що код зсередини for-block може бачити зовнішній код, але код у зовнішньому блоці не може бачити код всередині.

Оскільки зовнішній код не може бачити всередині, він не може використовувати все, що оголошено всередині. Але оскільки код у блоці for-for може бачити і всередині, і зовні, змінна, оголошена в обох місцях, не може бути використана однозначно за назвою.

Тож ви цього не бачите, або ви C #!


0

Подивіться на це так само, як якщо б ви могли оголосити intв usingблоці:

using (int i = 0) {
  // i is in scope here
}
// here, i is out of scope

Однак, оскільки intне реалізується IDisposable, цього зробити не можна. Це може допомогти комусь уявити, як intзмінна розміщується в приватному масштабі.

Іншим способом було б сказати:

if (true) {
  int i = 0;
  // i is in scope here
}
// here, i is out of scope

Сподіваюсь, це допомагає візуалізувати, що відбувається.

Мені дуже подобається ця функція, оскільки декларування intз forциклу всередині зберігає код приємно і щільно.


WTF? Якщо ви збираєтеся позначити NEG, майте кулі, щоб сказати, чому.
jp2code

Я не є прихильником, і я вважаю, що порівняння з ним usingцілком нормальне, хоча семантика є досить різною (тому, можливо, це було спровоковано). Зауважте, що if (true)це зайве. Видаліть його, і у вас є блок визначення.
Авель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.