CodeGym /Kurslar /C# SELF /Hadisə emalı: lambda-ifadələr

Hadisə emalı: lambda-ifadələr

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

1. Giriş

Müasir C# layihələrinin çoxunda hadisələrin işlənməsi üçün demək olar ki, "adi" metodlar istifadə olunmur. Belə yaranıb çünki lambda-ifadələr abunə yerlərində (operator +=) dərhal və qısa şəkildə handler yaratmağa imkan verir — əgər işləmə sadədirsə və başqa yerlərdə təkrar istifadə olunmursa. Bu sanki kiçik bir qeydi birbaşa kofe maşının üzərinə yapışdırmaq kimidir: "bu düyməni bas" — ətraflı təlimat faylı yazıb ayrıca qovluqda saxlamaqdansa. Əgər vəzifə lokal və birdəfədirsə, lambda mükəmməldir!

Praktikada harada istifadə olunur

  • ASP.NET-də (məsələn, səhifənin lifecycle hadisələrinin işlənməsində),
  • WPF/WinForms UI üçün (məsələn, düyməyə klikdə),
  • Server proqramlaşdırmada (məsələn, pipeline daxilində loqika),
  • Testlərdə, handler-a ayrıca ad lazım olmadıqda.

Sintaksis: kodda necə görünür

Gəlin əvvəlcə adi hadisə işlənməsini nəzərdən keçirək:


// Hadisənin elan edilməsi
public event EventHandler? MyEvent;

// Hadisəyə adi metodla abunə
void Handler(object? sender, EventArgs e)
{
    Console.WriteLine("Hadisə baş verdi!");
}

public void Subscribe()
{
    MyEvent += Handler;
}

İndi — lambda-ifadəsi ilə variant (anonim funksiya):


public void Subscribe()
{
    MyEvent += (sender, e) => Console.WriteLine("Hadisə baş verdi (lambda)!");
}

Nəzərinizə çatdırım: biz ayrıca metod yaratmırıq, handler-ı birbaşa abunə olarkən təyin edirik. Lambdanın imzası avtomatik olaraq hadisənin tipi ilə uyğun gəlir (EventHandler).

Yanaşmaların müqayisəsi

Adi metod Lambda
Kod həcmi Çox (metod + abunə) Az, hər şey yerində
Təkrar istifadəlilik Təkrar istifadə oluna bilər Adətən yox
Kodun lokalılığı Yayılmış Hər şey yaxın
Aydınlıq/oxunaqlılıq Mürəkkəb loqika üçün yaxşıdır Sadə hallar üçün əla

2. Hadisə emalı və lambdalar ilə tətbiq

Əsas struktur


public class Menu
{
    public event EventHandler? ItemSelected;

    public void SelectItem(int index)
    {
        Console.WriteLine($"Menyu elementi {index} seçildi.");
        ItemSelected?.Invoke(this, EventArgs.Empty);
    }
}

Lambda-ifadəsi ilə abunə


class Program
{
    static void Main()
    {
        var menu = new Menu();

        // Lambda vasitəsilə hadisəyə abunə
        menu.ItemSelected += (sender, e) =>
        {
            Console.WriteLine("Seçiminiz üçün təşəkkürlər! Lambda-handler işə düşdü.");
        };

        menu.SelectItem(1);
    }
}

Gözlənilən çıxış:

Menyu elementi 1 seçildi.
Seçiminiz üçün təşəkkürlər! Lambda-handler işə düşdü.

Xarici kontekstdən dəyişənlərin ələ keçirilməsi

Lambda-ifadənin üstünlüklərindən biri — o, xarici scope-dan (görünüş sahəsindən) dəyərləri "yadda saxlayır" (closure). Məsələn, neçə dəfə menyu elementi seçildiyini saymaq: counter dəyişəni closure vasitəsilə ələ keçiriləcək.


static void Main()
{
    var menu = new Menu();
    int counter = 0;

    menu.ItemSelected += (s, e) =>
    {
        counter++;
        Console.WriteLine($"Element {counter} dəfə seçildi!");
    };

    menu.SelectItem(1);
    menu.SelectItem(2);
}

Gözlənilən çıxış:

Menyu elementi 1 seçildi.
Element 1 dəfə seçildi!
Menyu elementi 2 seçildi.
Element 2 dəfə seçildi!

Bu closure-in sehridir: counter lambdanın içində yaşamağa davam edir!

Hadisə parametrləri ilə nümunə

Əgər hadisəniz EventHandler<T> istifadə edirsə və T əlavə məlumat daşıyan öz sinifinizdirsə, lambda-ifadə sadəcə lazımi imzaya "uyğunlaşır".


public class MenuItemSelectedEventArgs : EventArgs
{
    public int ItemIndex { get; }
    public string Description { get; }

    public MenuItemSelectedEventArgs(int itemIndex, string description)
    {
        ItemIndex = itemIndex;
        Description = description;
    }
}

public class Menu
{
    public event EventHandler<MenuItemSelectedEventArgs>? ItemSelected;

    public void SelectItem(int index, string description)
    {
        Console.WriteLine($"Menyu elementi {index}: {description} seçildi.");
        ItemSelected?.Invoke(this, new MenuItemSelectedEventArgs(index, description));
    }
}

// İstifadə
static void Main()
{
    var menu = new Menu();

    // Hadisə arqumentlərinin unpack edilməsi ilə lambda
    menu.ItemSelected += (sender, args) =>
    {
        Console.WriteLine($"Seçilmiş element #{args.ItemIndex}: {args.Description.ToUpper()}");
    };

    menu.SelectItem(3, "Proqram haqqında");
}

Gözlənilən çıxış:

Menyu elementi 3: Proqram haqqında seçildi.
Seçilmiş element #3: PROQRAM HAQQINDA

3. Lokal handler-lar və lambda

Lambda-lar idealdır, əgər:

  • İşləmə loqikası qısa və aydındır,
  • Handler yalnız bir yerdə istifadə olunur,
  • Local kontekstdən dəyişənləri "capture" etmək lazımdır.

Əgər işləmə mürəkkəbdirsə, təkrar istifadə tələb olunursa və ya elan olunma yerindən kənarda çağıra bilən bir şeydirsə, ayrı adlı metoddan istifadə etmək daha yaxşıdır.

Nümunə: loqika içində və kənarda

Lambda (ideal):


button.Click += (s, e) => MessageBox.Show("Düymə basıldı!");

Metodlar (loqika daha mürəkkəb və ya təkrar tələb olunursa):


button.Click += Button_Click;

void Button_Click(object sender, EventArgs e)
{
    if (UserConfirmed())
    {
        SaveData();
        MessageBox.Show("Məlumatlar yadda saxlanıldı!");
    }
}

4. Alt pərdə: lambda-handler-larla nə baş verir

Lambda da delegatdır, adi handler kimi. Kompilyator anonim metod yaradır, və əgər həmin lambdada ələ keçirilmiş dəyişənlər varsa, onların saxlanması üçün gizli bir sinif də yaradılır.

Əhəmiyyətli məsələ (və tez rast gəlinən səhv): əgər lambdanı dövr daxilində yaradıb hadisəyə abunə edirsinizsə, bütün iterasiyalar eyni dövr dəyişənini ələ keçirə bilər.


for (int i = 0; i < 5; i++)
{
    buttons[i].Click += (sender, e) =>
    {
        Console.WriteLine($"Düyməyə klik # {i}");
    };
}

Bütün handler-lar üçün düymə nömrəsi 5 ola bilər! Bunun qarşısını almaq üçün dövr daxilində dəyişənin surətini yaradın:


for (int i = 0; i < 5; i++)
{
    int buttonIndex = i; // Lokal surət
    buttons[i].Click += (sender, e) =>
    {
        Console.WriteLine($"Düyməyə klik # {buttonIndex}");
    };
}

İndi hər şey gözlənildiyi kimi işləyəcək.

Lambda-handler-ların əsas gücü: real həyatda

Belə lambda-ifadələr vaxt qazandırır və inkişafı sürətləndirir, xüsusən loqika sadə olduqda. Onlar kodu "vəzifəyə yaxın" saxlayır, onu müxtəlif fayl və klasslara səpələmirlər. Real layihələrdə onları geniş görəcəksiniz: UI hadisələrindən tutmuş asinxron event bus-lara abunələrə qədər.

5. Tipik səhvlər

Səhv №1: uzunömürlü obyektlər üçün unsubscribe (-=) unudulması.
Əgər subscriber hadisəni nəşriyata abunə olub və unsubscribe etməyibsə, delegat vasitəsilə nəşr edən subscriber-ə referans saxlayır — garbage collector onu toplaya bilməz, hətta obyektə başqa referans yoxdursa belə. Nəticədə yaddaş "sızmaları" və asılı obyektlər yaranır, xüsusən nəşr edən uzunömürlü olarsa (statik hadisələr, singleton-lar, servis-lər).
Necə qaçınmalı: boşaltma/nisbi məhv etmə zamanı həmişə unsubscribe edin (məsələn, Dispose, OnDisable, OnDestroy). Çətin ssenarilərdə weak events / WeakEventManager, event manager pattern və ya IObservable/Rx nəzərə alın.

Səhv №2: dövrdən dəyişən ələ keçirilməsi olmadan capture edilməsi.
Tipik tələyə düşmək — dövrdə abunə yazmaq və closure-in eyni dövr dəyişənini ələ keçirməsidir, nəticədə bütün handler-lar final dəyəri görür, yaradılarkənki dəyəri yox. Bu gözlənilməz nəticələrə gətirir (bütün handler-lar eyni rəqəmi çap edir və s.).
Necə qaçınmalı: dövr daxilində lokal surət yaradın və onu capture edin:


for (int i = 0; i < n; i++)
{
    int current = i;
    button.Click += (s, e) => Handle(current);
}

Və ya lazımi dəyəri wrapper metodu vasitəsilə ötürün. Bu daha etibarlıdır və məqsədi aydın göstərir.

Səhv №3: abunə yerində ağır lambdaların istifadəsi.
Əgər abunə zamanı anonim funksiyanın içində böyük biznes loqikasını yerləşdirirsinizsə, kod oxunması çətinləşir, test etmək və debug etmək çətinləşir. Həmçinin anonim lambdalardan düzgün unsubscribe etmək çətindir, çünki eyni delegat instansiyası lazımdır. Nəticədə loqika kod boyunca yayılır və struktur itir.
Necə qaçınmalı: mürəkkəb loqikanı adlı metodlara və ya servislərə çıxarın; lazım gəldikdə delegatı dəyişəndə/field-də saxlayın ki, unsubscribe etmək mümkün olsun; lambdada yalnız yüngül wrapper/yenidən yönləndirmə saxlayın. Bu oxunaqlılığı və idarəolunmanı yaxşılaşdırır.

1
Sorğu/viktorina
, səviyyə, dərs
Əlçatan deyil
C#-da Hadisələr
Delegatlarla hadisələrin yaradılması
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION