CodeGym /Kurslar /C# SELF /Closures

Closures

C# SELF
Səviyyə, Dərs
Mövcuddur

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.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Daxili verilənlərlə (closure) sadə funksiya yaradılması
Daxili verilənlərlə (closure) sadə funksiya yaradılması
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION