Перейти к основному содержимому

Переменные и их области видимости

Где что объявлять

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

Антипаттерны

  • "Бессмертные" public static поля для хранения состояния приложения.
  • Имена x, tmp, data2 в бизнес-логике.
  • Очень широкая область видимости "на всякий случай".

Смежные статьи

Переменные и их области видимости

Разработчику Архитектору

Переменные

Теория — переменные, scope и типы

Общие определения, классификация (локальные/глобальные, статика/динамика типов), иерархия областей видимости, пространства имён и защита памяти — жизненный цикл переменных (4.03).

Система типов C# — типизация, преобразование типов.

Play ITЗагрузка интерактивного демо…

Play ITЗагрузка интерактивного демо…


Переменные

Переменная (Variable) – это именованное место в памяти, где хранится значение определённого типа. Она позволяет программе запоминать, использовать и изменять данные в процессе выполнения.

Базовый шаблон объявления переменной с явным указанием типа:

<тип> <имя>;

Примеры реализации:

int age;
string username;
bool isActive;
double price;

Разбор:

  • Каждая строка здесь — объявление переменной: задаётся тип и имя, но значение пока не присвоено.
  • int, bool, double — значимые типы; string — ссылочный тип.
  • До инициализации локальные переменные нельзя использовать в выражениях — компилятор выдаст ошибку.
  • Имена age, username, isActive, price отражают смысл данных, что улучшает читаемость.

Определение:

  • тип данных — определяет, какие данные может хранить переменная (int, string, bool и т.д.);
  • имя: уникальный идентификатор, по которому к переменной можно обратиться;
  • значение — то, что хранится в переменной (например, 25).

Объявление и инициализация переменной:

int age; // Объявление
age = 25; // Инициализация

string message = "Добро пожаловать"; // Одновременное объявление и инициализация

Разбор:

  • Первая пара строк показывает раздельные шаги: сначала объявление, затем присваивание.
  • age = 25; записывает значение в уже объявленную переменную.
  • string message = "Добро пожаловать"; объединяет объявление и инициализацию в одной инструкции.
  • Комментарии в примере фиксируют полезное различие между "объявить" и "инициализировать".

Здесь:

  • int, string - тип;
  • age, message - имя;
  • 25, "Добро пожаловать" - значение.

Синтаксис прост:

тип имя = значение; // инициализация

Разбор:

  • Это короткий шаблон, где переменная сразу получает стартовое значение.
  • Такой подход снижает риск использования неинициализированных данных.
  • Компилятор проверяет, что выражение справа совместимо с указанным типом.
  • Шаблон чаще всего используется в прикладном коде по умолчанию.

или

тип имя; // объявление
имя = значение; // присваивание

Разбор:

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

Переменная должна быть инициализирована, прежде чем использоваться (если только это не out-параметр или поле с инициализатором по умолчанию).

Для сокращения записи, когда тип очевиден из правой части, используют var — тип по-прежнему определяется на этапе компиляции (приведения и var). В новых проектах включайте nullable reference types.

Где можно использовать переменную — зависит от области видимости (scope).

Локальные переменные объявляются внутри методов, циклов или блоков и доступны только в пределах блока {…}, в котором они созданы:

void PrintGreeting()
{
string message = "Привет!"; // локальная переменная
Console.WriteLine(message);
} // message уничтожается здесь

if (true)
{
int x = 10;
Console.WriteLine(x); // OK
}
// Console.WriteLine(x); // ОШИБКА: x не существует

Разбор:

  • message существует только внутри метода PrintGreeting, потому что объявлена в его блоке { ... }.
  • После выхода из метода локальная переменная недоступна: её область видимости заканчивается.
  • Переменная x объявлена во внутреннем блоке if, поэтому доступна только внутри этого блока.
  • Попытка обратиться к x снаружи — ошибка компиляции, что и демонстрирует последняя строка.

В C# нет глобальных переменных, как в C или Python. Но есть статические поля класса, которые по сути ведут себя как "глобальные":

public class AppState
{
public static string CurrentUser = "admin";
}

Разбор:

  • public class AppState объявляет класс-контейнер для состояния приложения.
  • public static string CurrentUser — статическое поле: принадлежит типу, а не экземпляру объекта.
  • Значение "admin" инициализируется один раз для всего приложения (в рамках домена загрузки).
  • Доступ к полю идёт через имя класса: AppState.CurrentUser.

Доступ: AppState.CurrentUser. Чрезмерное использование таких полей — антипаттерн (нарушает инкапсуляцию). Обычно можно сделать отдельный статический класс, например, констант, который будет хранить набор чего-то статического для сравнения.


Константы

Константы (Constants) – значения, которые не могут быть изменены во время выполнения программы.

Синтаксис:

const тип Имя = значение;

Константы должны быть инициализированы при объявлении, и являются статическими по умолчанию (принадлежат классу, а не объекту). Значение подставляется на этапе компиляции (не занимает память во время выполнения). Поддерживает только примитивные типы, string, и enum. И константа является статической по умолчанию (принадлежит типу, а не экземпляру).

Отличие от переменной также в том, что константа не требует памяти – значение подставляется компилятором. Это нужно для фиксированных значений.

Шаблон объявления константы:

const <тип> <ИМЯ_КОНСТАНТЫ> = <значение>;

Разбор:

  • Обобщённая запись для объявления константы.
  • Имя обычно выбирают осмысленное, чтобы отражать назначение фиксированного значения.
  • Значение подставляется компилятором и не изменяется во время выполнения.
  • Используется для параметров, которые не должны меняться от запуска к запуску.

Примеры реализации:

const int MaxAttempts = 3;
const string DefaultCurrency = "RUB";
const double Pi = 3.14159;

Разбор:

  • const объявляет константы времени компиляции — их значения нельзя изменить после объявления.
  • MaxAttempts задаёт фиксированное число попыток, часто используемое в проверках и циклах.
  • DefaultCurrency хранит неизменяемую строковую настройку по умолчанию.
  • Pi показывает числовую константу для формул; компилятор подставляет её значение напрямую.

var

При работе с переменными, нужно использовать описательные имена, которые дадут понять, что они собой представляют – username, totalPrice, а не x, y.

C# позволяет не указывать тип явно, если он очевиден из правой части выражения.

Если тип очевиден из контекста, можно объявить через var:

var имя = значение;

И это самое распространённое, очень часто можно увидеть именно такой вид - var:

var list = new List<string>(); // тип string понятен
var age = 25; // int
var name = "Alice"; // string
var list = new List<int>(); // List<int>

Разбор:

  • Пример показывает вывод типа компилятором из правой части выражения.
  • Для age выводится int, для namestring.
  • Для коллекций выводится полный generic-тип (List<string> или List<int>).
  • В одном блоке дважды использовано имя list; в реальном коде в одной области видимости это вызовет ошибку о повторном объявлении.

Если тип неочевиден, лучше предусмотреть и строго типизировать. К примеру:

var result = GetData(); // Что за тип? int? string? object?

Правило: var — не динамический тип. Это синтаксический сахар. Тип определяется на этапе компиляции и не меняется. Помните, что язык C# не динамически типизированный, а строго - это значит, что есть требования к указаниям типов.

Шаблон вывода типа через ключевое слово var:

var <имя> = <значение>;

Разбор:

  • Шаблон с var сокращает запись, когда тип очевиден из правой части.
  • Несмотря на краткость, тип остаётся статическим и проверяется компилятором.
  • Такой вариант особенно полезен для длинных generic-типов.
  • Если тип неочевиден читателю, лучше указать его явно.

Примеры реализации:

var count = 10; // int
var name = "Alex"; // string
var users = new List<User>(); // List<User>
var coordinates = (x: 10, y: 20); // ValueTuple

Модификаторы параметров

ПодробноПередача параметров в C# (числа, объекты, ref, out, in).

ref — передача по ссылке (с возможностью изменения). Переменная должна быть инициализирована до передачи. Метод может изменить её значение.

void DoubleValue(ref int x)
{
x *= 2;
}

int number = 5;
DoubleValue(ref number); // number = 10

Разбор:

  • ref int x передаёт аргумент по ссылке: метод работает с исходной переменной вызывающего кода.
  • x *= 2; изменяет значение на месте, а не копию.
  • DoubleValue(ref number) обязан указывать ref и в сигнатуре, и в месте вызова.
  • После вызова number становится 10, так как изменение произошло в исходной переменной.

Полезно, когда нужно изменить исходную переменную.

Шаблон передачи параметра по ссылке с возможностью изменения:

ref <тип> <имяПараметра>

Разбор:

  • Описывает параметр, передаваемый по ссылке с возможностью изменения.
  • Метод работает с исходной переменной, а не с копией.
  • Требует явного использования ref и при объявлении, и при вызове.
  • Применяется, когда нужно модифицировать аргумент вызывающей стороны.

Пример реализации в C#:

void Increment(ref int value)
{
value += 1;
}

int counter = 5;
Increment(ref counter); // counter становится 6

out — выходной параметр. Переменная не обязана быть инициализирована при передаче, но должна быть присвоена внутри метода.

Код ITЗагрузка примера кода…

Разбор:

  • out int result означает, что метод обязан присвоить параметру значение перед завершением.
  • Внутри используется int.TryParse, который и валидирует строку, и заполняет result.
  • Возвращаемый bool сигнализирует об успехе разбора, а result содержит само число.
  • В вызове out int value одновременно объявляется новая переменная value.

Идеально для методов, возвращающих результат и статус (например, TryParse).

Шаблон выходного параметра:

out <тип> <имяПараметра>

Разбор:

  • Шаблон для выходного параметра: метод возвращает дополнительное значение через аргумент.
  • Переменная может быть неинициализированной до вызова.
  • Внутри метода обязательна инициализация out-параметра.
  • Часто используется вместе с булевым статусом успешности.

Пример реализации в C#:

bool TryParseInt(string input, out int result)
{
return int.TryParse(input, out result);
}

if (TryParseInt("42", out int number))
{
Console.WriteLine(number); // 42
}

Ключевые слова ref и out используются для передачи аргументов в методы по ссылке, что позволяет изменять значение переменной внутри метода и сохранять эти изменения после завершения метода. ref - когда нужно передать уже существующее значение в метод и позволить методу изменить его, out - когда метод должен вернуть несколько значений (например, для возврата результата и состояния ошибки).

in — передача по ссылке, только для чтения (C# 7.2+). Позволяет передавать большие структуры по ссылке, но запрещает их изменение.

void PrintPerson(in Person person)
{
Console.WriteLine(person.Name);
// person.Name = "xxx"; // ОШИБКА: нельзя изменять
}

var p = new Person { Name = "Bob" };
PrintPerson(in p);

Разбор:

  • in Person person передаёт аргумент по ссылке в режиме "только чтение".
  • Это снижает стоимость копирования для крупных структур и запрещает изменение параметра в методе.
  • Строка с присваиванием закомментирована, потому что компилятор не даст изменить in-параметр.
  • На месте вызова также можно явно указать in, чтобы подчеркнуть семантику передачи.

Эффективно для производительности: избегает копирования, но безопасно.

Шаблон передачи параметра по ссылке только для чтения:

in <тип> <имяПараметра>

Разбор:

  • Описывает параметр, передаваемый по ссылке только для чтения.
  • Подходит для больших структур, когда копирование нежелательно.
  • Запрещает изменение параметра в методе, что делает API безопаснее.
  • Комбинирует производительность ссылочной передачи и семантику неизменяемости.

Пример реализации в C#:

void LogMessage(in string message)
{
Console.WriteLine($"[LOG] {message}");
}

string msg = "System started";
LogMessage(in msg);

params — переменное число аргументов. Позволяет передавать произвольное количество аргументов одного типа.

void PrintNumbers(params int[] numbers)
{
foreach (int n in numbers)
Console.WriteLine(n);
}

PrintNumbers(1, 2, 3, 4); // OK
PrintNumbers(); // OK — пустой массив

Разбор:

  • params int[] numbers разрешает передавать произвольное количество аргументов типа int.
  • Внутри метода они доступны как обычный массив numbers.
  • foreach последовательно выводит каждый переданный элемент.
  • Вызов без аргументов корректен: метод получает пустой массив.

Шаблон переменного числа аргументов:

params <тип>[] <имяПараметра>

Пример реализации в C#:

void PrintAll(params string[] messages)
{
foreach (var msg in messages)
Console.WriteLine(msg);
}

PrintAll("A", "B", "C"); // три аргумента
PrintAll(); // ноль аргументов (пустой массив)

Хранение данных в переменной

Что можно хранить в переменной?

  • Переменная может содержать:
  • Примитивные значения — int, bool, double, char;
  • Ссылки на объекты — string, List<T>, MyClass;
  • Результат вызова метода
var length = GetName().Length;
var user = CreateUser();

Разбор:

  • GetName().Length показывает, что в переменную можно сохранять результат вычисления, а не только литералы.

  • Тип length будет выведен как int, потому что свойство Length возвращает целое число.

  • CreateUser() возвращает объект пользователя, и тип user выводится из возвращаемого типа метода.

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

    • Анонимные объекты:
var person = new { Name = "Alice", Age = 30 };

Разбор:

  • new { ... } создаёт анонимный тип с автоматически сгенерированными свойствами.

  • Свойства Name и Age типизируются как string и int соответственно.

  • Анонимные типы удобны для временных проекций (например, в LINQ), когда отдельный класс создавать нецелесообразно.

  • Обычно применяются в пределах локального метода, так как имя типа недоступно в явном виде.

    • Функции (делегаты):
Func<int, int> square = x => x * x;

Разбор:

  • Func<int, int> описывает делегат: принимает int и возвращает int.
  • Лямбда x => x * x реализует функцию возведения числа в квадрат.
  • Переменная square хранит ссылку на исполняемый код, который можно вызывать как обычный метод.
  • Такой подход полезен для передачи поведения в методы высшего порядка (Where, Select и т.д.).

Переменные используются также в циклах.

foreach (var item in items)
{
Console.WriteLine(item);
}

Разбор:

  • foreach итерируется по всем элементам коллекции items без работы с индексами.
  • var item — переменная текущего элемента, автоматически типизируемая по типу коллекции.
  • На каждой итерации вызывается Console.WriteLine(item), то есть выводится текущее значение.
  • Переменная item существует только внутри тела цикла и недоступна снаружи.

Здесь item - локальная переменная, объявленная на каждой итерации. Она копируется из коллекции:

  • Для значимых типов (int, struct) — копируется значение.
  • Для ссылочных типов (class) — копируется ссылка (объект общий).

Изменение item не повлияет на коллекцию. Для foreach по массиву item — readonly. Чтобы изменить элемент, используйте for.

Шаблон локальной переменной в блоке кода:

{
<тип> <имя> = <значение>;
// <имя> доступно здесь
}
// <имя> недоступно здесь

Пример реализации:

void Process()
{
int outer = 10;

if (true)
{
int inner = 20;
Console.WriteLine(outer + inner); // OK: 30
}

// Console.WriteLine(inner); // Ошибка: переменная не существует
Console.WriteLine(outer); // OK: 10
}

Шаблон переменной в цикле:

for (<тип> <имя> = <начальное>; <условие>; <шаг>)
{
// <имя> доступно здесь
}
// <имя> недоступно здесь

Пример реализации:

for (int i = 0; i < 5; i++)
{
Console.WriteLine(i);
}
// i недоступна после цикла

foreach (var item in collection)
{
Console.WriteLine(item);
}
// item недоступна после цикла

Шаблон поля экземпляра класса:

<модификаторДоступа> <тип> <имяПоля>;

Пример реализации:

public class User
{
private string _username; // приватное поле
public int Age; // публичное поле (не рекомендуется)
protected DateTime CreatedAt; // защищённое поле
}

Шаблон статического поля:

static <тип> <имяПоля>;

Пример реализации:

public class Configuration
{
public static string ApiKey = "secret";
private static int _requestCount = 0;
}

Шаблон доступа к полю текущего объекта:

this.<имяПоля>

Пример реализации в C#:

public class Calculator
{
private int _value;

public void SetValue(int value)
{
this._value = value; // this указывает на поле класса
}
}

Шаблон доступа к статическому полю:

<ИмяКласса>.<имяПоля>

Пример реализации:

int count = Configuration.RequestCount;
Configuration.ApiKey = "new-key";

Шаблон доступа к свойству объекта:

<имяОбъекта>.<имяСвойства>

Пример реализации:

User user = new User();
user.Name = "Alice"; // установка значения
string name = user.Name; // получение значения

Шаблон доступа к элементу коллекции:

<имяКоллекции>[<индекс>]
<имяКоллекции>[<ключ>]

Пример реализации:

int[] numbers = { 1, 2, 3 };
int first = numbers[0]; // 1

Dictionary<string, int> scores = new();
scores["Alice"] = 100;
int aliceScore = scores["Alice"]; // 100

Шаблон анонимного типа:

var <имя> = new { <имяСвойства1> = <значение1>, <имяСвойства2> = <значение2> };

Пример реализации в C#:

var person = new { Name = "Alice", Age = 30 };
Console.WriteLine(person.Name); // Alice

Шаблон кортежа:

var <имя> = (<значение1>, <значение2>, ...);
(<тип1>, <тип2>, ...) <имя> = (<значение1>, <значение2>, ...);

Пример реализации в C#:

var point = (10, 20);
Console.WriteLine(point.Item1); // 10

(int x, int y) coordinates = (30, 40);
Console.WriteLine(coordinates.x); // 30