1. Giriş
Scope-u xatırlayırıq
Gəlin təsəvvür edək ki, dəyişənlər — böyük bir ofisin işçiləridir, metodlar, dövrələr və kod blokları isə otaqlar və kabinetlərdir. Bəzi işçiləri ancaq öz otağına buraxırlar, digərləri isə ümumiyyətlə bütün binada gəzə bilər. Kim harada ola bilər — bax bu onların scope-u (scope) olur.
Scope müəyyən edir ki, elan olunan dəyişən proqramda harada “görünür” və harada istifadə oluna bilər.
Scope-un əsas növləri
C#-da əsasən belə scope-lar var:
| Scope | Nümunə | Dəyişən harada "görünür" |
|---|---|---|
| Lokal | Metodun və ya blokun içində | Ancaq həmin blokun içində |
| Metod parametri | Metodun signature-ında | Ancaq metodun içində |
| Sinif dəyişəni (field) | Sinifin bədənində, metodlardan kənarda | Bu sinifin bütün metodlarında |
| Dövrənin/şərtin içində dəyişən | dövrənin/if-in içində |
Ancaq həmin içində |
İzahlı nümunə
public class Office
{
int buildingNumber = 50; // Sinifin field-i: bütün metodlarda görünür public void PrintInfo() {
int roomNumber = 101; // Lokal dəyişən: ancaq PrintInfo-da görünür if (roomNumber > 100) {
int deskNumber = 5; // Ancaq bu if blokunda görünür Console.WriteLine(deskNumber);
} Console.WriteLine(deskNumber); //
Xəta! deskNumber burada artıq görünmür
}
}
2. Lokal funksiyalar və scope
Kim kimi "görür"?
Əgər sən lokal funksiyanı metodun içində (hətta dövrənin və ya şərtin içində) elan edirsənsə, o, yuxarıda elan olunan dəyişənlərlə eyni scope-da olur. Lokal funksiya — sanki “eyni kabinetin bir hissəsidir”.
Nümunə
Lokal funksiya ətraf scope-dakı dəyişənləri görür
void PrintWithPrefix(string message)
{
string
prefix = "[LOG]: "; void Print() {
Console.WriteLine(
prefix +
message); // hər iki dəyişəni görür!
} Print();
}
Burada prefix və message dəyişənləri lokal Print funksiyasının içində görünür, çünki onlar eyni və ya daha geniş scope-da elan olunub.
Burada neçə scope var?
Yuxarıdakı nümunədə:
- PrintWithPrefix metodunun scope-u var
- onun içində — Print funksiyasının scope-u var
3. Dəyişənlərin tutulması (Capture)
Dəyişənlərin tutulması — bu o vəziyyətdir ki, lokal funksiya özündən kənarda, amma eyni scope-da elan olunan dəyişənləri istifadə edir.
Lokal funksiyalar və anonim metodlar (lambda ifadələri, ona bir az sonra baxacağıq) onlara elan olunanda əlçatan olan bütün dəyişənləri yadda saxlayır (və ya “tutur”).
Belə demək olar ki, funksiyalar ətraf aləmin bir snapshot-unu (capture) çəkir — və bu dəyişənləri çox sonralar çağırılanda belə istifadə edə bilir.
Qrafik olaraq
Main metodu
└─ dəyişən x
└─ lokal funksiya F() ← "tutur" x-i
Nümunə — ən sadə capture
void CounterExample()
{
int counter = 0;
void Increase()
{
counter++; // Bu funksiya counter dəyişənini tutur
}
Increase();
Increase();
Console.WriteLine(counter); // 2 çıxacaq
}
Burada iki dəfə Increase lokal funksiyası çağırılandan sonra counter dəyəri 2 olur.
4. Dəyişənlərin tutulmasının tətbiqi
Dəyişənlərin tutulması imkan verir ki, metodun scope-u ilə lokal funksiyalar arasında məlumatı rahat “ötürəsən”, əlavə parametrlərlə əziyyət çəkmədən.
Əgər capture olmasaydı, bütün dəyişənləri parametr kimi ötürməli olardın:
void CounterExampleWithoutCapture()
{
int counter = 0;
void Increase(ref int c)
{
c++;
}
Increase(ref counter);
Increase(ref counter);
Console.WriteLine(counter);
}
Bu narahatdır — niyə hər dəfə ref yazıb funksiyanın signature-ını korlayasan ki, əgər o, rahatlıqla xaricdəki dəyişənləri görə bilirsə?
5. Lokal funksiyalar və dəyişənlərin metoddan sonra yaşaması
Dəyişənlər metoddan sonra yaşayır?
Əgər lokal funksiyanı (və ya lambda ilə delegate-i) cari metoddan kənara ötürürsənsə, tutulan dəyişənlər avtomatik olaraq “ölmür” — CLR (.NET-in virtual maşını) hər şeyi yadda saxlayacaq, lazım olan hər şey yaddaşda “qulaqdan asılı” qalacaq.
Nümunə: funksiyalar metoddan kənarda yaşayır
Func<int> GetCounter()
{
int count = 0;
int Increment()
{
count++;
return count;
}
return Increment; // Funksiyanı çölə qaytarırıq!
}
var counter = GetCounter();
Console.WriteLine(counter()); // 1
Console.WriteLine(counter()); // 2
Burada, GetCounter metodu bitəndən sonra belə, count dəyişəni yaşayır, çünki qaytarılan funksiya onu tutub. Buna closure deyilir — bu barədə ayrıca mühazirədə danışacağıq, amma lokal funksiyalar səviyyəsində də eyni mexanizmdir.
6. Tipik səhvlər və maraqlı ssenarilər
Lokal funksiya çağırılmamışdan əvvəl dəyişənin yenilənməsi
Bəzən belə olur ki, lokal funksiya tərəfindən tutulan dəyişən, funksiya çağırılmamışdan əvvəl dəyişir — və nəticə gözlədiyindən fərqli olur.
Nümunə:
void Example()
{
int x = 42;
void PrintX() { Console.WriteLine(x); }
x = 100; // Dəyişəni dəyişdik!
PrintX(); // 100 çıxacaq, 42 yox!
}
Trik: Lokal funksiya çağırılan anda dəyişənin ən son dəyərini görür.
for/foreach dövrəsində dəyişənlərin tutulması (bir daha)
Klassik ağrı: əgər sən interview-da və ya böyük layihədə kod yazırsansa, həmişə yoxla: görəsən, dövrədən “canlı” dəyişəni tuturam, və o necə davranacaq.
GO TO FULL VERSION