CodeGym /Kursy /C# SELF /Przechwytywanie zmiennych przez funkcje lokalne

Przechwytywanie zmiennych przez funkcje lokalne

C# SELF
Poziom 11 , Lekcja 5
Dostępny

1. Wprowadzenie

Przypominamy sobie zakres (scope)

Wyobraź sobie, że zmienne to pracownicy wielkiego biura, a metody, pętle i bloki kodu to pokoje i gabinety. Niektórych pracowników wpuszcza się tylko do swojego pokoju, a innych — do całego budynku. To, gdzie kto może przebywać — to właśnie jego zakres (scope).

Zakres określa, gdzie zadeklarowana zmienna jest “widoczna” w programie i gdzie można jej używać.

Podstawowe rodzaje zakresów

W C# można wyróżnić takie główne zakresy:

Zakres Przykład Gdzie "widoczna" jest zmienna
Lokalny Wewnątrz metody lub bloku Tylko w tym bloku
Parametr metody W sygnaturze metody Tylko w tej metodzie
Zmienna klasy (pole) W ciele klasy poza metodami We wszystkich metodach tej klasy
Zmienna w pętli/warunku Wewnątrz
{}
pętli/if
Tylko w tych
{}

Przykład z wyjaśnieniami

public class Office
{
    
  
int buildingNumber = 50; // Pole klasy: widoczne we wszystkich metodach public void PrintInfo() {
int roomNumber = 101; // Zmienna lokalna: widoczna tylko w PrintInfo if (roomNumber > 100) {
int deskNumber = 5; // Widoczna tylko w tym bloku if Console.WriteLine(deskNumber);
} Console.WriteLine(deskNumber); // Błąd! deskNumber tutaj już nie jest widoczna
}
}

2. Funkcje lokalne i zakres

Kto kogo "widzi"?

Kiedy deklarujesz funkcję lokalną wewnątrz metody (albo nawet w pętli czy warunku), ona znajduje się w tym samym zakresie co zmienne zadeklarowane wyżej. Funkcja lokalna to jakby “część tego samego pokoju”.

Przykład

Funkcja lokalna widzi zmienne z otaczającego zakresu


    void PrintWithPrefix(string wiadomosc)
{
    
  
string prefix = "[LOG]: "; void Print() {
Console.WriteLine( prefix + wiadomosc); // widzi obie zmienne!
} Print();
}

Tutaj zmienne prefix i wiadomosc są widoczne wewnątrz funkcji lokalnej Print, bo są zadeklarowane w tym samym lub szerszym zakresie.

Ile tu jest zakresów?

W powyższym przykładzie:

  • jest zakres metody PrintWithPrefix
  • w nim — zakres funkcji Print
Funkcje lokalne nie tworzą całkiem osobnego “pokoju”: mogą widzieć wszystko, co jest na korytarzu (w metodzie).

3. Przechwytywanie zmiennych (Capture)

Przechwytywanie zmiennych — to sytuacja, gdy funkcja lokalna używa zmiennych, które zostały zadeklarowane poza tą funkcją, ale w tym samym zakresie.

Funkcje lokalne i anonimowe metody (wyrażenia lambda, do nich jeszcze dojdziemy) zapamiętują (czyli “przechwytują”) wszystkie zmienne, które były dla nich dostępne w momencie deklaracji.

Można powiedzieć, że funkcje robią jakby zdjęcie (capture) otaczającego świata — i mogą używać tych zmiennych nawet wtedy, gdy są wywoływane dużo później.

Schematycznie

Metoda Main
└─ zmienna x
└─ funkcja lokalna F() ← "przechwytuje" x

Przykład — najprostsze przechwycenie

void CounterExample()
{
    int licznik = 0;

    void Zwieksz()
    {
        licznik++; // Ta funkcja przechwytuje zmienną licznik
    }

    Zwieksz();
    Zwieksz();
    Console.WriteLine(licznik); // Wypisze 2
}
Funkcja lokalna przechwytuje zmienną z zewnętrznego zakresu

Tutaj po dwóch wywołaniach funkcji lokalnej Zwieksz wartość licznik zwiększa się do 2.

4. Zastosowanie przechwytywania zmiennych

Przechwytywanie zmiennych pozwala wygodnie “przekazywać” dane między zakresem metody a funkcjami lokalnymi, bez męczenia się z dodatkowymi parametrami.

Gdyby nie było przechwytywania, musiałbyś przekazywać wszystkie zmienne jako parametry:

void CounterExampleWithoutCapture()
{
    int licznik = 0;

    void Zwieksz(ref int c)
    {
        c++;
    }

    Zwieksz(ref licznik);
    Zwieksz(ref licznik);
    Console.WriteLine(licznik);
}

To niewygodne — po co ciągle pisać ref i psuć sygnaturę funkcji, skoro ona może łatwo “widzieć” zmienne z zewnątrz?

5. Funkcje lokalne i życie zmiennych po wyjściu z metody

Czy zmienne żyją dłużej niż metoda?

Jeśli przekazujesz funkcje lokalne (albo delegaty z lambdami) gdzieś poza bieżącą metodę, przechwycone zmienne automatycznie przestają “umierać” po wyjściu z metody. CLR (.NET virtual machine) się tym zajmie — wszystko co potrzebne będzie “trzymane na siłę” w pamięci.

Przykład: funkcje żyją poza metodą

Func<int> GetCounter()
{
    int liczba = 0;
    int Zwieksz()
    {
        liczba++;
        return liczba;
    }
    return Zwieksz; // Zwracamy funkcję na zewnątrz!
}

var licznik = GetCounter();
Console.WriteLine(licznik()); // 1
Console.WriteLine(licznik()); // 2
Closure: zmienna liczba żyje po wyjściu z metody

Tutaj, nawet po zakończeniu metody GetCounter, zmienna liczba dalej żyje, bo zwrócona funkcja ją przechwyciła. To się nazywa closure (zamknięcie) — do tego jeszcze wrócimy w osobnym wykładzie, ale na poziomie funkcji lokalnych mechanizm jest taki sam.

6. Typowe błędy i ciekawe scenariusze

Nadpisanie zmiennej przed wywołaniem funkcji lokalnej

Czasem możesz się spotkać z tym, że zmienna, którą przechwytuje funkcja lokalna, zostaje zmieniona przed jej wywołaniem — i wtedy wynik może być inny niż się spodziewasz.

Przykład:


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

    x = 100; // Zmienna została zmieniona!
    PrintX(); // Wypisze 100, a nie 42!
}

Sztuczka: Funkcja lokalna zawsze widzi najnowszą wartość zmiennej w momencie wywołania.

Przechwytywanie zmiennych w pętli for/foreach (jeszcze raz)

Klasyczny ból: jeśli piszesz logikę na rozmowie kwalifikacyjnej albo w dużym projekcie, zawsze sprawdzaj: czy nie przechwytuję “żywej” zmiennej z pętli i jak ona się zachowa.

1
Ankieta/quiz
Krotki i funkcje lokalne, poziom 11, lekcja 5
Niedostępny
Krotki i funkcje lokalne
Zalety krotek w porównaniu do out i typów anonimowych
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION