1. Вступ
Пригадуємо область видимості
Уявімо, що змінні — це працівники великого офісу, а методи, цикли й блоки коду — кімнати та кабінети. Деяких працівників пускають лише до своєї кімнати, а інших — до всієї будівлі. Те, де хто може перебувати, — і є їхня область видимості (scope).
Область видимості визначає, де оголошена змінна видима у програмі, і де її можна використовувати.
Основні типи областей видимості
У C# можна виокремити такі основні області видимості:
| Область | Приклад | Де змінна видима |
|---|---|---|
| Локальна | Всередині методу або блоку | Лише всередині цього блоку |
| Параметр методу | У сигнатурі методу | Лише всередині методу |
| Змінна класу (поле) | У тілі класу поза методами | У всіх методах цього класу |
| Змінна всередині циклу/умови | Всередині циклу/if |
Лише всередині цих |
Приклад з поясненнями
public class Office
{
int buildingNumber = 50; // Поле класу: видно у всіх методах public void PrintInfo() {
int roomNumber = 101; // Локальна змінна: видна тільки всередині PrintInfo if (roomNumber > 100) {
int deskNumber = 5; // Видна тільки всередині цього блоку if Console.WriteLine(deskNumber);
} Console.WriteLine(deskNumber); //
Помилка! deskNumber тут вже не видна
}
}
2. Локальні функції й область видимості
Хто кого «бачить»?
Коли ви оголошуєте локальну функцію всередині методу (або навіть всередині циклу чи умови), вона опиняється у тій самій області видимості, що й змінні, оголошені вище. Локальна функція — ніби «частина цього ж кабінету».
Приклад
Локальна функція бачить змінні з навколишньої області.
void PrintWithPrefix(string message)
{
string
prefix = "[ЛОГ]: "; void Print() {
Console.WriteLine(
prefix +
message); // бачить обидві змінні!
} Print();
}
Тут змінні prefix і message видимі всередині локальної функції Print, оскільки вони оголошені у тій самій або ширшій області.
Скільки тут областей?
У прикладі вище:
- є область методу PrintWithPrefix
- всередині неї — область функції Print
3. Захоплення змінних (Capture)
Захоплення змінних — це ситуація, коли локальна функція використовує змінні, які були оголошені поза цією функцією, але у тій самій області видимості.
Локальні функції й анонімні методи (лямбда-вирази, до них ще дійдемо трохи пізніше) запам’ятовують (або «захоплюють») усі змінні, які їм були доступні під час оголошення.
Можна сказати, що функції ніби роблять знімок (capture) навколишнього світу — і можуть використовувати ці змінні навіть тоді, коли викликаються набагато пізніше.
Схематично
Метод Main
└─ змінна x
└─ локальна функція F() ← "захоплює" x
Приклад — найпростіше захоплення
void CounterExample()
{
int counter = 0;
void Increase()
{
counter++; // Ця функція захоплює змінну counter
}
Increase();
Increase();
Console.WriteLine(counter); // Виведе 2
}
Тут після двох викликів локальної функції Increase значення counter збільшується до 2.
4. Застосування захоплення змінних
Захоплення змінних дозволяє зручно «передавати» дані між областю методу й локальними функціями без додаткових параметрів.
Якби захоплення не було, вам довелося б передавати всі змінні як параметри:
void CounterExampleWithoutCapture()
{
int counter = 0;
void Increase(ref int c)
{
c++;
}
Increase(ref counter);
Increase(ref counter);
Console.WriteLine(counter);
}
Це незручно — навіщо постійно писати ref та псувати сигнатуру функції, якщо вона й так «бачить» змінні ззовні?
5. Локальні функції і життя змінних після виходу з методу
Чи живуть змінні довше методу?
Якщо ви передаєте локальні функції (або делегати з лямбдами) кудись поза поточний метод, захоплені змінні автоматично не «помирають» після виходу з нього. CLR (віртуальна машина .NET) подбає — усе потрібне утримується в пам’яті.
Приклад: функції живуть поза методом
Func<int> GetCounter()
{
int count = 0;
int Increment()
{
count++;
return count;
}
return Increment; // Повертаємо функцію назовні!
}
var counter = GetCounter();
Console.WriteLine(counter()); // 1
Console.WriteLine(counter()); // 2
Тут, навіть після завершення методу GetCounter, змінна count продовжує жити, оскільки повернена функція її захопила. Це називають замиканням (closure) — ми до цього ще дійдемо в окремій лекції, але на рівні локальних функцій механізм такий самий.
6. Типові помилки та цікаві сценарії
Перезапис змінної до виклику локальної функції
Іноді можна зіткнутися з тим, що змінна, яку захоплює локальна функція, змінюється до її виклику — і тоді результат може відрізнятися від очікуваного.
Приклад:
void Example()
{
int x = 42;
void PrintX() { Console.WriteLine(x); }
x = 100; // Змінну змінили!
PrintX(); // Виведе 100, а не 42!
}
Зверніть увагу: локальна функція завжди «бачить» останнє значення змінної на момент виклику.
Захоплення змінних у циклі for/foreach (ще раз)
Класична пастка: якщо ви пишете код на співбесіді або у великому проєкті, завжди перевіряйте, чи не захоплюєте «живу» змінну з циклу і як вона поводитиметься.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ