CodeGym /Kurslar /C# SELF /Lambdalara və müqayisə üçün delegatlara giriş

Lambdalara və müqayisə üçün delegatlara giriş

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

1. Giriş

Təsəvvür elə ki, səndə tələbələrin siyahısı var və onları bir dəfə yaşa görə, sonra soyada görə, sonra isə orta bala görə sıralamalısan, amma yalnız bütün imtahanları vermiş olanlar üçün. Əgər hər belə "birdəfəlik" müqayisə üçün ayrıca IComparer<T> interfeysini implement edən yeni bir sinif yazsan, layihən tez bir zamanda kiçik comparator siniflərinin zibilliyinə çevriləcək. Bu əlverişsizdir: kod həcmli və oxunmaz olur.

Belə hallarda C# daha zərif həll təklif edir: müqayisə məntiqini birbaşa Sort metoduna ayrıca sinif yaratmadan ötürmək imkanı.

Bunun üçün bizə delegatlar və onların qısa qardaşları – lambda ifadələri lazım olacaq.

Delegatlar – bizim çevik köməkçilərimiz

Lambdalara keçməzdən əvvəl, gəlin baxaq delegat nədir. Sadə dillə desək, delegat – bu, metoda istinad saxlayan tipdir. Bir az mistik səslənir, düzdür? Belə düşün:

Təsəvvür elə ki, səndə görüləcək işlərin siyahısı var və bəziləri "təlimat" və ya "resept" kimidir. Delegat – bu, belə "reseptə" (metoda) istinad saxlaya bilən xüsusi dəyişəndir. Sonra isə, bu işi görmək lazım olanda, sadəcə delegat dəyişəninə müraciət edirsən və o, istinad etdiyi metodu "çağırır".

C#-da delegatlar callback-lər (metodu sonradan çağırmaq, adətən hadisəyə reaksiya kimi), event-lərin işlənməsi (məsələn, düyməyə klik) və əlbəttə, metodlara başqa metodları arqument kimi ötürmək üçün istifadə olunur (yəni metod başqa metodu çağıra bilsin) – bizə indi məhz bu sort üçün lazımdır.

List<T>.Sort() metodunun bir neçə overload-u (versiyası) var və onlardan biri xüsusi Comparison<T> delegatını qəbul edir.

2. Comparison<T> delegatı

Comparison<T> nədir?

Comparison<T> – .NET-də daxili olan və eyni tipli iki obyektin müqayisəsi üçün xüsusi hazırlanmış delegatdır, burada T tipdir. Onun "resepti" belədir: iki T tipli obyekt qəbul edir (onlara xy deyək) və tam ədəd (int) qaytarır:

  • Mənfi ədəd (məsələn, -1), əgər x "y-dən kiçikdirsə".
  • Sıfır (0), əgər x "y-yə bərabərdirsə".
  • Müsbət ədəd (məsələn, 1), əgər x "y-dən böyükdürsə".

Bu qaydalara əsasən həm IComparable.CompareTo, həm də IComparer.Compare işləyir. Yəni məntiq eynidir, sadəcə indi onu ayrıca sinif yox, "metod dəyişəni" kimi ötürə bilərik.

Gəlin nümunəyə baxaq. Tələbələrimizə qayıdaq. Tutaq ki, bizdə Student sinfi var:

public class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public double AverageGrade { get; set; }

    public Student(string firstName, string lastName, int age, double averageGrade)
    {
        FirstName = firstName;
        LastName = lastName;
        Age = age;
        AverageGrade = averageGrade;
    }

    public void PrintInfo()
    {
        Console.WriteLine($"Tələbə: {FirstName} {LastName}, Yaş: {Age}, Bal: {AverageGrade:F2}");
    }
}

İndi tələbələri yaşa görə sıralamaq üçün delegatdan istifadə edərək ayrıca statik metod yaza bilərik, hansı ki, Comparison<Student> imzasına uyğundur:

public class Program
{
    // Comparison<Student> delegatının imzasına uyğun metod
    // İki tələbəni yaşa görə müqayisə edir
    public static int CompareStudentsByAge(Student student1, Student student2)
    {
        // Rəqəmlər üçün daxili CompareTo metodundan istifadə edirik,
        // hansı ki, müqayisəyə görə -1, 0 və ya 1 qaytarır.
        return student1.Age.CompareTo(student2.Age);
    }

    public static void Main(string[] args)
    {
        List<Student> students = new List<Student>
        {
            new Student("İvan", "Petrov", 20, 4.5),
            new Student("Mariya", "Sidorova", 22, 4.8),
            new Student("Aleksey", "İvanov", 19, 3.9),
            new Student("Elena", "Kozlova", 20, 4.2) // Eyni yaşda iki tələbə
        };

        Console.WriteLine("--- Sıralamadan əvvəl tələbələrin siyahısı ---");        
        foreach (var s in students)
            s.PrintInfo();

        Console.WriteLine("--- Tələbələri yaşa görə sıralayırıq (delegatla) ---");
        students.Sort(CompareStudentsByAge); //CompareStudentsByAge metodunu parametr kimi ötürürük

        foreach (var s in students)
            s.PrintInfo();
    }
}

Kodu izah edirik:

  1. Biz statik CompareStudentsByAge metodu yaratdıq, hansı ki, iki tələbə qəbul edir və int qaytarır, Comparison<Student> müqaviləsinə uyğun.
  2. Main-də tələbələrin siyahısını yaratdıq.
  3. students.Sort(CompareStudentsByAge); çağıranda, CompareStudentsByAge() metodunu dərhal çağırmırıq! Sadəcə bu metoda istinad ötürürük. List<T>.Sort() sonra özü lazım olduğu qədər CompareStudentsByAge metodunu müxtəlif tələbə cütləri üçün çağıracaq. Bu, sanki kiməsə çatdırılma ünvanı verirsən, amma dərhal bütün yük maşınını göndərmirsən.

Bu yanaşma hər xırda sıralama üçün ayrıca comparator sinifi yaratmaqdan qat-qat rahatdır. Amma daha da rahat etmək olar!

3. Lambda ifadələri ilə tanış olun

Hətta CompareStudentsByAge kimi ayrıca metod yazmaq da artıq görünə bilər, əgər müqayisə məntiqi sadədirsə və cəmi bir-iki dəfə lazımdır. Belə hallar üçün C#-da lambda ifadələri (lambda expressions) yaradılıb.

Lambda ifadəsi nədir? Əslində, bu anonim metoddur və ya mən zarafatla deyirəm, "evsiz metod". Bu, kodun kiçik bir parçasını (metodu) birbaşa lazım olan yerdə yazmaq üsuludur, ayrıca elan etmədən. Sanki tez bir təlimatı stikerə yazıb birbaşa işə yapışdırırsan, tam manual yazmaq əvəzinə.

Lambda ifadəsinin əsas operatoru =>-dır (oxunur: "ox" və ya "keçir"). O, giriş parametrlərini metodun bədənindən ayırır.

Lambda ifadəsinin əsas sintaksisi

Tutaq ki, səndə delegat (metoda istinad) var və onu Sort() funksiyasına ötürürsən:

public static int CompareStudentsByAge(Student student1, Student student2)
{
   return student1.Age.CompareTo(student2.Age);
}

students.Sort(CompareStudentsByAge); //CompareStudentsByAge metodunu parametr kimi ötürürük

Bunu daha qısa yazmaq olar:

//Anonim metodu parametr kimi ötürürük
students.Sort( (Student student1, Student student2) => student1.Age.CompareTo(student2.Age) );

Burada metodun adının yerinə iki əsas şeyi qoyuruq:

  • parametrlər: (Student student1, Student student2)
  • metodun içi: student1.Age.CompareTo(student2.Age)

Belə qısa metod yazılışına lambda ifadəsi deyilir: (parametrlər) => ifadə

Necə işləyir

C# kompilyatoru kodda lambda ifadəsi görəndə, onun üçün əsl metod yaradır.

Tutaq ki, belə kodun var:

students.Sort( (s1, s2) => s2.AverageGrade.CompareTo(s1.AverageGrade) );

Kompilyasiya nəticəsi təxminən belə olacaq:

public static int CompareStudents_Lambda123(Student s1, Student s2)
{
   return s2.AverageGrade.CompareTo(s1.AverageGrade);
}

students.Sort( CompareStudents_Lambda123 );

4. Sıralama nümunəsi və lambda ifadələri

Gəlin tələbələrlə olan nümunəmizi lambda ifadəsi ilə yazaq:

public class Program
{
    public static void Main(string[] args)
    {
        List<Student> students = new List<Student>
        {
            new Student("İvan", "Petrov", 20, 4.5),
            new Student("Mariya", "Sidorova", 22, 4.8),
            new Student("Aleksey", "İvanov", 19, 3.9),
            new Student("Elena", "Kozlova", 20, 4.2)
        };

        Console.WriteLine("--- Sıralamadan əvvəl tələbələrin siyahısı ---");        
        foreach (var s in students)
            s.PrintInfo();

        // İndi müqayisə məntiqi birbaşa burada, "yerində" yazılıb
        Console.WriteLine("--- Tələbələri yaşa görə sıralayırıq (lambda ifadəsi ilə) ---");
        students.Sort((student1, student2) => student1.Age.CompareTo(student2.Age));

        foreach (var s in students)
            s.PrintInfo();

        // Azalan sıralama üçün sadəcə nəticəni -1-ə vururuq
        Console.WriteLine("\n--- Tələbələri orta bala görə sıralayırıq (azalan) ---");
        // s2.CompareTo(s1) əvəzinə s1.CompareTo(s2)
        students.Sort((s1, s2) => s2.AverageGrade.CompareTo(s1.AverageGrade));

        foreach (var s in students)
            s.PrintInfo();
    }
}

Burada nə baş verdi?

  1. students.Sort((student1, student2) => student1.Age.CompareTo(student2.Age));
    • student1student2 – bunlar Sort tərəfindən anonim metodumuza ötürüləcək parametrlərdir (analojik olaraq xy Comparison<T>-də).
    • => – lambda operatorudur.
    • student1.Age.CompareTo(student2.Age) – lambda ifadəsinin bədənidir. Burada bu, sadəcə bir ifadədir və qaytarılan dəyərdir.
  2. Orta bala görə azalan sıralama üçün sadəcə s1s2-nin yerlərini CompareTo-da dəyişdik. Bu, sıralamanı tərsinə çevirmək üçün klassik fənddir.

Niyə bu rahatdır?

  • Qısa: Hər kiçik müqayisə məntiqi üçün ayrıca metod və ya sinif yaratmağa ehtiyac yoxdur.
  • Oxunaqlı: Müqayisə məntiqi birbaşa Sort() çağırışının yanında olur, bu da kodun başa düşülməsini asanlaşdırır, xüsusilə sadə hallarda.
  • Çevik: Sıralama şərtlərini "yolda" asanlıqla dəyişmək olar.

5. Delegatlar və Lambdalar – ideal cütlük

Sual yarana bilər: bəs lambda ifadəsi delegatla eynidir, yoxsa fərqlidir?

Əslində, lambda ifadəsi sadəcə sintaksis şəkəri-dir (syntax sugar) – delegat və ya expression tree yaratmaq üçün. Kompilyator lambda ifadəsini görəndə, onu avtomatik olaraq uyğun delegat instansiyasına çevirir. Bizim halda, List<T>.Sort() Comparison<T> gözlədiyinə görə, kompilyator başa düşür ki, (student1, student2) => student1.Age.CompareTo(student2.Age) məhz Comparison<Student>-ə çevrilməlidir.

Beləliklə, lambda ifadələri bizə çox qısa kod yazmağa imkan verir, delegatlar isə bu kodu "daşıyan" və icra edən konteynerlərdir. Onlar əl-ələ işləyir!

Nə vaxt hansını istifadə etməli?

  • IComparable<T>: Tipiniz üçün təbii, aydın sıralama varsa istifadə edin. Məsələn, məhsulları sıralayırsınızsa və əsas sıralama üsulu onların artikuluna görədirsə. Bu interfeys "default" sıralama qaydasını müəyyən edir.
  • IComparer<T>: Çoxsaylı, təkrar istifadə olunan müqayisə məntiqi lazım olanda istifadə edin, amma əsas sinifi "çirkləndirmək" istəmirsinizsə və ya bir neçə fərqli sıralama üsulunuz varsa. Məsələn, bir IComparer məhsulları qiymətə görə sıralayır, digəri ada görə və siz onları proqramın müxtəlif yerlərində istifadə edirsiniz.
  • Delegatlar (Comparison<T>) və Lambda ifadələri: Birdəfəlik, ad-hoc sıralamalar üçün idealdır, müqayisə məntiqi sadədirsə və ayrıca sinifə ehtiyac yoxdursa. Bu, C#-da ən çox istifadə olunan və təmiz üsuldur. Həmçinin, filtrasiya (Find, FindAll) və axtarış (FindIndex) metodlarına məntiq ötürmək üçün də əladır – bunları əvvəlki dərslərdə baxmışdıq.
Xüsusiyyət IComparable<T> IComparer<T> Comparison<T> / Lambda ifadəsi
Harada müəyyən olunur? Birbaşa T sinifində Ayrı comparator sinifində Metod və ya anonim ifadə ola bilər
Çeviklik Sabit "təbii" sıralama Çoxsaylı, təkrar istifadə olunan sıralamalar Ad-hoc (yolda), konkret metod çağırışı üçün
Boilerplate Az, sinifin içində Orta (ayrı sinif) Minimum (xüsusilə lambdalar üçün)
İstifadə nümunəsi
someList.Sort()
someList.Sort(new MyComparer())
someList.Sort((x, y) => x.Prop.CompareTo(y.Prop))
Oxunaqlılıq Təbii sıralama üçün yaxşıdır Comparatorun adına bağlıdır Sadə, spesifik müqayisələr üçün əladır

6. Praktik tətbiq və gələcəyə baxış

Lambda ifadələri sadəcə sıralama üçün "sintaksis şəkəri" deyil. Bu, müasir C# kodunda hər yerdə istifadə olunan güclü alətdir. Onları çox tez-tez görəcəksən:

  • LINQ-də (Language Integrated Query): Lambda ifadələrinin ən geniş istifadə olunduğu yer bura. LINQ kolleksiyalara SQL-ə bənzər sorğular yazmağa imkan verir və lambdalar filtrasiya, sıralama, məlumatların proyeksiyası üçün şərtləri müəyyən edir. Tezliklə LINQ öyrənəcəyik və görəcəksən ki, lambdalar onu necə güclü və rahat edir.
  • Event-lərin işlənməsində: Lambdalar hadisə baş verəndə (məsələn, UI-də düyməyə klik) nə olacağını qısa şəkildə yazmağa imkan verir.
  • Asinxron proqramlaşdırmada: Paralel yerinə yetiriləcək tapşırıqları müəyyən etmək üçün.
  • .NET-in müxtəlif API-lərində: Standart kitabxanadakı bir çox metodlar parametr kimi delegat (və beləliklə, lambda ifadəsi) qəbul edir ki, çevik məntiq əlavə olunsun.

Beləliklə, lambda ifadələrini mənimsəməklə, təkcə sıralama bacarığını artırmırsan, həm də müasir C# kodunu və kitabxanalarını başa düşmək üçün böyük addım atırsan. Bu bacarıq istənilən müsahibədə qiymətləndiriləcək və hər layihədə lazım olacaq!

7. Tipik səhvlər və incəliklər

Delegat və lambda ifadələri ilə müqayisə aparanda bir neçə məqama diqqət yetirmək lazımdır:

Səhv müqayisə nəticəsi: Yadda saxla ki, CompareTo və ya müqayisə məntiqin mənfi ədəd, sıfır və ya müsbət ədəd qaytarmalıdır. Əgər təsadüfən başqa bir şey qaytarsan, sıralama səhv işləyə və ya hətta xətaya səbəb ola bilər. Ən çox rast gəlinən səhv – yeni başlayanlar true və ya false qaytarır, halbuki int qaytarmaq lazımdır. Sort metodu məhz rəqəm gözləyir, çünki ona elementlərin yalnız bərabər olub-olmadığı yox, həm də hansı daha "böyükdür" lazımdır.

null dəyərlərin işlənməsi: Əgər kolleksiyanda elementlər null ola bilərsə, null obyekt üzərində metod çağırmaq (məsələn, student1.Age.CompareTo(...), əgər student1 null-dırsa) NullReferenceException-a səbəb olacaq. Belə hallarda müqayisə məntiqin null dəyərləri açıq şəkildə işləməlidir. Ümumi qayda budur ki, null istənilən qeyri-null dəyərdən "kiçik" sayılır. Hər ikisi null-dırsa, bərabərdir. Biri null, digəri yoxdursa, null "kiçik"dir.

// Müqayisə üçün lambda ifadəsində null-un işlənməsi nümunəsi
students.Sort((s1, s2) => {
    if (s1 == null && s2 == null) return 0;
    if (s1 == null) return -1; // null hər şeydən kiçikdir
    if (s2 == null) return 1;  // null olmayan null-dan böyükdür
    return s1.Age.CompareTo(s2.Age); // Hər ikisi null deyil, müqayisə edirik
});

Xoşbəxtlikdən, real layihələrdə kolleksiyalarda çox vaxt null olmur, amma bu məqamı bilmək vacibdir!

Performans: Lambda ifadələri çox rahatdır, amma bəzən onları çox "isti" döngülərdə və ya çox böyük kolleksiyalarda həddindən artıq istifadə etmək performansa bir az təsir edə bilər, müqayisədə yüksək optimizasiya olunmuş IComparer sinifləri ilə, hansı ki, bəlkə də xüsusi test və profil olunub. Amma gündəlik tapşırıqlar üçün fərq əhəmiyyətsizdir və kodun oxunaqlılığı və sadəliyi üstünlük təşkil edir.

Çətin müqayisə zəncirləri: Soyada, sonra ada görə sıralama nümunəsində gördüyümüz kimi, lambda ifadələri bir neçə şərti iç-içə yazmağa imkan verir. Bu, bir sətrdə on if yazmaqdan qat-qat rahatdır! Əsas odur ki, həmişə əvvəlcə ilk müqayisənin nəticəsini yoxla (lastNameComparison != 0), sonra növbəti səviyyəyə keç.

Lambda ifadələri və delegatlar – C#-da əsas anlayışlardır, hansılar ki, daha çevik və funksional proqramlaşdırma tərzinə yol açır. Onları başa düşmək və tətbiq etmək kodunu xeyli təmiz, effektiv və müasir edəcək. Təcrübə etməyə davam et və tezliklə onları avtomatik istifadə edəcəksən!

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION