JavaRush /Курси /C# SELF /Захоплення змінних локальними функціями

Захоплення змінних локальними функціями

C# SELF
Рівень 11 , Лекція 5
Відкрита

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
Замикання: змінна count живе після виходу з методу

Тут, навіть після завершення методу GetCounter, змінна count продовжує жити, оскільки повернена функція її захопила. Це називають замиканням (closure) — ми до цього ще дійдемо в окремій лекції, але на рівні локальних функцій механізм такий самий.

6. Типові помилки та цікаві сценарії

Перезапис змінної до виклику локальної функції

Іноді можна зіткнутися з тим, що змінна, яку захоплює локальна функція, змінюється до її виклику — і тоді результат може відрізнятися від очікуваного.

Приклад:


void Example()
{
    int x = 42;
    void PrintX() {  Console.WriteLine(x);  } 

    x = 100; // Змінну змінили!
    PrintX(); // Виведе 100, а не 42!
}

Зверніть увагу: локальна функція завжди «бачить» останнє значення змінної на момент виклику.

Захоплення змінних у циклі for/foreach (ще раз)

Класична пастка: якщо ви пишете код на співбесіді або у великому проєкті, завжди перевіряйте, чи не захоплюєте «живу» змінну з циклу і як вона поводитиметься.

1
Опитування
Кортежі та локальні функції, рівень 11, лекція 5
Недоступний
Кортежі та локальні функції
Переваги кортежів над out і анонімними типами
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ