Область видимости и время жизни переменных


Все переменные, которые мы использовали до этого момента, объявлялись в начале метода Main(). Но в C# можно объявлять локальные переменные и внутри блока. Как уже говорилось в главе 1, блок начинается открывающейся фигурной скобкой и заканчивается закрывающей фигурной скобкой. Он определяет область видимости, которая зависит от того, имеет ли данный блок вложенные блоки. Каждый раз, создавая блок, вы создаете новую область видимости, определяющую время жизни объявляемых переменных.

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

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

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

Чтобы понять это правило, рассмотрим следующую программу:

// В программе демонстрируется зависимость возможности доступа к
// переменной от области видимости, в которой она была объявлена.

using System;

class ScopeDemo {
public static void Main() {
int x; // Переменная x известна всему коду
// в пределах метода Main().

x = 10;
if(x == 10) { //Создается новая область видимости.
int y = 20; // Эта переменная будет видна только в рамках // этого блока.

// В данном блоке видны обе переменные, x и y.
Console.WriteLine("Видны x и y: " + x + " " + y);
x = y * 2;
}
// y = 100; // Ошибка! Переменная y здесь не видна!
y - это переменная, объявленная во вложенной области видимости, поэтому здесь она не видна.

// Выполняется доступ к переменной x, которая была объявлена
// в этой области видимости.
Console.WriteLine("Значение переменной x равно: " + x);
}
}

Как указано в комментариях, объявленная в методе Main() переменная x доступна всему коду в пределах этого метода. Переменная y объявляется в рамках блока if. Поскольку блок определяет область видимости, переменная y является видимой только в пределах самого блока. Вот почему использование переменной y в строке y = 100; вне своего блока является ошибкой, о чем и говорится в комментарии. Если вы удалите начальный символ комментария, произойдет ошибка компилирования, так как переменная y невидима за пределами своего блока. В рамках блока if может использоваться переменная x, поскольку в блоке (вложенной области видимости) код имеет доступ к переменным, объявленным во внешней области видимости.

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

Обратите внимание на еще один важный момент: переменные создаются при их объявлении в какой-либо области видимости (при входе в данную область) и уничтожаются при выходе из этой области. Это означает, что переменная не будет сохранять свое значение при выходе из своей области видимости, то есть переменная, объявленная в пределах метода, не будет хранить свои значения между вызовами этого метода. Также переменная, объявленная в рамках блока, будет терять свое значение при выходе из блока. Следовательно, время жизни переменной ограничено ее областью видимости.

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

// В программе демонстрируется использование правила,
// определяющего время жизни переменной.

using System;

class VarInitDemo {
public static void Main() {
int x;

for(x = 0; x < 3; x++) {
int y = -1; // Переменная y инициализируется каждый раз
// при входе в блок.
Console.WriteLine("Значение переменной y равно: " + y);
// Всегда выводится значение -1.
y = 100;
Console.WriteLine("Теперь значение переменной y равно: " + y);
}
}
}

Ниже показан результат выполнения этой программы:

Значение переменной y равно: -1
Теперь значение переменной y равно: 100
Значение переменной y равно: -1
Теперь значение переменной y равно: 100
Значение переменной y равно: -1
Теперь значение переменной y равно: 100

Как видите, переменная y инициализируется значением -1 каждый раз при входе во внутренний цикл for, затем ей присваивается значение 100, которое утрачивается при завершении цикла.

Заметим, что между языками C# и С есть отличие в работе с областями видимости.

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

/*
В этой программе делается попытка объявить во внутренней области
видимости переменную с тем же именем, что и у переменной,
объявленной во внешней области видимости.

*** Эта программа не будет скомпилирована. ***
*/

using System;

class NestVar {
public static void Main() {
int count;

Переменную count объявлять нельзя, поскольку переменнаё с таким именем уже объявлена в методе Main().

for(count = 0; count < 10; count = count+1) {
Console.WriteLine("Счет проходов цикла: " + count);

int count; // Это объявление недействительно!!!
for(count = 0; count < 2; count++)
Console.WriteLine("В программе есть ошибка!");
}
}
}

Если у вас есть опыт программирования на С/С++, то вам известно, что в этих языках нет ограничений для имен переменных, объявляемых во внутренней области видимости. То есть в С/С++ объявление еще одной переменной count в цикле for является допустимым, но при этом данное объявление скроет внешнюю переменную. Чтобы скрытие переменной не приводило к ошибкам программирования, разработчики C# запретили такое действие.