1. Giriş
Proqramlaşdırmada closure — JavaScript-də qapını bağlamaq üçün üsul deyil, lambda-ifadənin və ya anonim metodun ətraf kontekstdən dəyişənləri capture edib həmin blok bitdikdən sonra da onları «yadda saxlaması» mexanizmidir. Sadə desək, closure — öz yarandığı şərtləri yadda saxlayan funksiyadır və bu dəyərləri kiçik bir çantaya (dəyişənlərə) bənzər formada saxlayır.
Closure — bu funksiya ilə onu yaratdığı zaman mövcud olmuş ətraf mühitin (scope) birlikdə olmasıdır.
Closure-un ən sadə nümunəsi
Praktikaya baxaq:
Func<int> MakeCounter()
{
int count = 0;
return () =>
{
count++;
return count;
};
}
Belə çağıraq:
var counter = MakeCounter();
Console.WriteLine(counter()); // 1
Console.WriteLine(counter()); // 2
Console.WriteLine(counter()); // 3
Bu necə işləyir?
- Dəyişən count MakeCounter metodunun daxilində elan olunub.
- Lambda () => { ... } xaricə qaytarılır və indi metoddən kənarda yaşayır.
- Amma! O count dəyişənini xatırlayır, hətta MakeCounter metodu artıq bitmiş olsa belə.
Elə budur — closure: lambda ətraf kontekstdən count dəyişənini «capture» edir.
Lambda nəyi «capture» edir?
- ətraf metoddakı lokal dəyişənləri (scope),
- metodun parametrlərini,
- blok daxilindəki dəyişənləri (for, foreach və s.).
Vacib: dəyişənlər value ilə deyil, reference ilə capture olunur! Əgər closure içində dəyişəni dəyişdirsək, o dəyişiklik «xaricdən» də görünəcək. Compiler C#-da bu dəyişənlər üçün xüsusi köməkçi sinif yaradır — amma konseptual olaraq bunu bilmək kifayətdir ki, closure-lardan rahat istifadə edəsiniz.
2. Closure və leksik scope
«Funksiya fabrikləri» nümunəsini closure ilə genişləndirək:
Func<int, int> PowerFactory(int power)
{
return x =>
{
int result = 1;
for (int i = 0; i < power; i++)
result *= x;
return result;
};
}
İstifadə:
var square = PowerFactory(2); // x^2
var cube = PowerFactory(3); // x^3
Console.WriteLine(square(5)); // 25
Console.WriteLine(cube(2)); // 8
square və cube funksiyaları power üçün fərqli dəyərlərlə yaradılıb və hər biri öz dəyərini xatırlayır. Hər PowerFactory çağırışı üçün «öz çantası» yaradılır.
3. Captured dəyişənlərin mutasiyası
Bəzən sual olur: loop daxilində bir neçə lambda yaratsaq və onlar loop-un dəyişənini capture etsələr nə olacaq? Burada asanlıqla tələyə düşmək olar.
Nümunə: loop-da closure
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions)
action(); // ???
Nə gözləyirsiniz? 0, 1, 2? Əslində belə olacaq:
3
3
3
Niyə belə? Bütün lambda-lar eyni i dəyişəninə «reference» edir. Loop bitdiyində i artıq 3-dür və bütün Action-lar bu dəyəri görür.
Düzəliş edilmiş variant
Hər lambda-nın öz dəyərini capture etməsi üçün loop-un daxilində yeni dəyişən yaradaq:
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
int copy = i;
actions.Add(() => Console.WriteLine(copy));
}
foreach (var action in actions)
action(); // 0 1 2
İndi copy hər iterasiyada yeni dəyişəndir və closure məhz onu capture edir.
4. Closure-ların real istifadə sahələri
Data işləmə və callback-lər
Asinxron işlər edərkən və ya icranı gecikdirəndə (event handler-lar, filter-ləmə, task scheduling) closure loqikanı və parametrləri bir yerdə «paketləməyə» imkan verir. Məsələn:
void ProcessList(List<int> list, int threshold)
{
var filtered = list.Where(x => x > threshold);
foreach (var item in filtered)
Console.WriteLine(item);
}
Burada Where içindəki lambda threshold dəyişənini capture edir.
Funksiya «fabrikləri» yaratmaq
Parametri ötürürsən — həmin parametr «daxili» olan funksiya alırsan. Bu üsul filter-ləmə, sort comparator-ları, UI reaksiyaları və s. üçün çox faydalıdır.
State idarəetməsi
Bəzən ayrıca class yaratmadan az miqdarda state saxlamaq lazımdır:
Func<string, string> CreateGreeting()
{
string prefix = "Hello";
return name =>
{
return $"{prefix}, {name}!";
};
}
5. Faydalı incəliklər
Alt qat: C#-da closure necə işləyir
Lambda tərəfindən capture olunan hər şey compiler tərəfindən köməkçi sinifə çevrilir: dəyişənlər sahələrə (fields), lambda isə metoda çevrilir. Buna görə dəyişənlərin state-i çağırışlar arasında yaşayır.
Hər «fabrika» kiçik obyekt yaradır. Bu normaldır — .NET bu obyektləri effektiv idarə edir və onları yalnız ehtiyac olduqda ayırır.
Qeyd: böyük obyektləri lazım olmayan halda capture etməyin
Əgər böyük obyekt (məsələn, UI formu) capture edilərsə, o lambda yaşadığı müddətcə azad olunmayacaq. Tipik yaddaş sızması səbəbi — lambda ilə event-ə subscribe etmək və unsubscribe etməmək (+=/-=).
Closure və kolleksiyaların həyatı — LINQ nümunəsi
Closure LINQ-ı çevik edir: filter-lər öz parametrlərini yadda saxlayır.
List<string> colors = new List<string> { "Red", "Green", "Blue", "Yellow" };
string startsWith = "B";
var filtered = colors.Where(c => c.StartsWith(startsWith));
foreach (var color in filtered)
Console.WriteLine(color); // "Blue"
Əgər sonra startsWith-ı dəyişsəniz, nəticə də dəyişəcək:
startsWith = "R";
foreach (var color in filtered)
Console.WriteLine(color); // "Red"
Belə olur, çünki closure eyni startsWith dəyişəninə referans edir, və StartsWith hər dəfə cari dəyəri yoxlayır.
6. Closure ilə işləyərkən tipik səhvlər
Səhv №1: çağırış anına qədər dəyişən dəyişdirilməsinin capture edilməsi.
Loop-larda klassik vəziyyət: closure içindəki lambda loop-un eyni dəyişəninə baxır, və çağırış anında o artıq başqa dəyər alıb. Nəticədə funksiya gözlənilən datalarla işləməyir. Həll — loop daxilində ayrıca bir dəyişən yaratmaq.
Səhv №2: çox geniş context-in capture edilməsi.
Closure bütün obyekti çəkib apara bilər, konkret field/qiymət yox. Bu lazımsız asılılıqlar yaradır və kodu çətinləşdirir. Yalnız lazım olanı capture edin.
Səhv №3: ağır resursların saxlanması və yaddaş sızması.
Lambda ilə event-ə subscribe etmək və həmin lambdanın «ağır» obyekt capture etməsi, amma unsubscribe etməmək — obyekt azad olunmur. Subscribe müddətinə diqqət edin və lazım olduqda unsubscribe edin (-=).
Səhv №4: kodun idarəolunmazlığı.
Closure-ların həddindən artıq istifadəsi məlumatların mənbəyini və dəyişikliklərini anlamağı çətinləşdirir, xüsusən closure çağırış yerindən uzaqda elan olunubsa. Logikanı yaxın saxlayın və həddindən artıq istifadə etməyin.
GO TO FULL VERSION