CodeGym /Kurslar /C# SELF /İnterfeys IComparer<T>

İnterfeys IComparer<T>

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

1. Giriş

Təsəvvür elə ki, sən universitetin dekanısan və burada çoxlu tələbə oxuyur. Sənə mütəmadi olaraq müxtəlif kriteriyalara görə sıralanmış siyahılar lazımdır:

  1. Ada görə (tələbəni əlifba sırası ilə tez tapmaq üçün).
  2. Orta bala görə (əlaçılara qrant vermək üçün).
  3. Yaşa görə (statistika, müsabiqələr və s. üçün).
  4. Kursa görə, kurs daxilində isə soyada görə.

Əgər biz yalnız IComparable<T>-ə güvənsəydik, bizim Tələbə sinifimiz cəmi bir müqayisə üsulu implementasiya etməli olardı. Məsələn, qərara gəlsəydik ki, tələbənin "təbii" sırası orta bala görədir. Əla, List.Sort() artıq işləyir! Amma bəs bizə ada görə sıralanmış siyahı lazımdırsa? Tələbə sinifi artıq bal üzrə müqayisə ilə "məşğuldur". O, eyni anda iki "təbii" sıralama qaydasına sahib ola bilməz. Bu, sanki sənin yalnız bir "boksda ən yaxşı olmaq" təlimatın var, amma təcili şahmatda ən yaxşı olmalısan və "boks" təlimatından istifadə etməyə çalışırsan. Çətin ki, uğur qazanasan, düzdür?

Məhz belə hallarda, eyni tip obyektləri müxtəlif üsullarla sıralamaq lazım olanda və müqayisə məntiqini sinifin özünə əlavə etmək istəməyəndə IComparer<T> interfeysi var. Bu, həm də o vaxt faydalıdır ki, sinifin mənbə koduna çıxışın yoxdur və ya sinif bütün sıralama üsullarını bilməməlidir.

2. IComparer<T> interfeysi

Əgər IComparable<T> — obyektin daxili hissiyatıdırsa, yəni özünü başqası ilə necə müqayisə edəcəyini bilir, IComparer<T> isə tam ayrı, xarici hakim və ya müstəqil arbitrdır ki, istənilən iki oyunçunu (obyekti) götürüb, əvvəlcədən müəyyən olunmuş qaydalara görə müqayisə edir.

Təsəvvür elə ki, sən futbol komandasının məşqçisisən. Kapitanı seçməlisən.

  • IComparable<T>: Hər oyunçu özü deyir: "Mən bu oğlandan güclüyəm, çünki daha sürətli qaçıram!" (Onun öz, daxili qaydası).
  • IComparer<T>: Sən, məşqçi kimi deyirsən: "Uşaqlar, bu gün kapitanı ötürmənin dəqiqliyinə görə seçirik. Pərviz, ötür! Vüsal, ötür! Əla, Pərviz daha dəqiq ötürdü! O, bu gün kapitan." (Bu sənin, xarici qaydandır, istənilən iki oyunçuya tətbiq olunur).

Tərif: IComparer<T> — .NET-də interfeysdir, bizə T tipli iki obyekt üçün xarici müqayisə məntiqi təyin etməyə imkan verir. Yəni, IComparer<T> implementasiya edən sinif müqayisə olunan obyektlərdən biri deyil; o, sadəcə Compare metodunu verir, hansı ki, iki obyekt qəbul edir və onların nisbi sırasını müəyyən edir.

Sintaksis

IComparer<T> interfeysinin sintaksisi çox sadədir:


public interface IComparer<T>
{
    // İki T tipli obyekti müqayisə edən metod
    // x - müqayisə üçün birinci obyekt
    // y - müqayisə üçün ikinci obyekt
    int Compare(T x, T y);
}

Compare(T x, T y) metodu IComparable<T>-dəki CompareTo(T other) metodu kimi işləyir:

  • Mənfi ədəd qaytarırsa, x "y-dən kiçikdir".
  • Sıfır (0) qaytarırsa, x "y-ə bərabərdir".
  • Müsbət ədəd qaytarırsa, x "y-dən böyükdür".

Bizim halda, "kiçik", "bərabər", "böyük" yalnız bizim Compare implementasiyasında təyin etdiyimiz müqayisə məntiqindən asılıdır.

IComparer<T> və IComparable<T> fərqi nədir?

Sinif / İnterfeys Harada implementasiya olunur Nəyə lazımdır İstifadə nümunəsi
IComparable<T>
Birbaşa tipdə (sinif/strukturda) Bir standart müqayisə üsulu ID-yə görə artan sıralama
IComparer<T>
Ayrı sinifdə İstənilən sayda müqayisə üsulu Ada, tarixə görə sıralama

3. IComparer<T> praktikada implementasiyası

Gəlin "böyük bir tətbiq"imizi davam etdirək — sadə istifadəçi modeli. Bizdə belə bir sinif olsun:


// Bizim istifadəçi sinifimiz
public class User
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }
}

Ada görə sıralama: ayrıca komparator yazırıq

Xüsusi bir sinif yazaq, hansı ki, IComparer<User> implementasiya edir və istifadəçiləri ada görə müqayisə edir:


// Ada görə sıralama üçün komparator sinifi
public class UserNameComparer : IComparer<User>
{
    public int Compare(User x, User y)
    {
        // null yoxlayırıq (sürpriz olmasın deyə!)
        if (ReferenceEquals(x, y)) return 0;
        if (x is null) return -1;   // null istənilən obyektdən "kiçikdir"
        if (y is null) return 1;

        // Ada görə müqayisə edirik (string sıralama standartını nəzərə alırıq)
        return string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
    }
}

Komparatoru siyahını sıralamaq üçün istifadə edirik:


List<User> users = new List<User>
{
    new User { Name = "İvan", Age = 20, Email = "ivan@mail.com" },
    new User { Name = "Anna", Age = 32, Email = "anna@gmail.com" },
    new User { Name = "Boris", Age = 28, Email = "boris@work.org" },
    new User { Name = "Ruslan", Age = 19, Email = "ruslan@yandex.ru" }
};

// IComparer ilə ada görə sıralayırıq
users.Sort(new UserNameComparer());

users.ForEach(u => Console.WriteLine(u.Name)); // Anna, Boris, İvan, Ruslan

Gördün necə rahat və şık oldu? Komparator sinifi proqramında müstəqil vətəndaş olur — onu istənilən başqa istifadəçi siyahısı üçün istifadə edə bilərsən.

4. Müxtəlif müqayisə variantları: fərqli komparatorlar yaradırıq

Maraqlısı budur ki, ürəyin istədiyi qədər komparator yarada bilərsən. Məsələn, gəlin yaşa görə sıralama implementasiya edək:


// Yaşa görə sıralama üçün komparator
public class UserAgeComparer : IComparer<User>
{
    public int Compare(User x, User y)
    {
        if (ReferenceEquals(x, y)) return 0;
        if (x is null) return -1;
        if (y is null) return 1;

        // Yaşın artan sırasına görə sıralayırıq
        return x.Age.CompareTo(y.Age);
    }
}

Və indi:


users.Sort(new UserAgeComparer());
users.ForEach(u => Console.WriteLine($"{u.Name} ({u.Age})"));
// Çıxış: Ruslan (19) İvan (20) Boris (28) Anna (32)

Əgər yaşa görə azalan sıralamaq istəsən, sadəcə arqumentlərin yerini dəyiş:


// Yaşa görə azalan sıralama üçün komparator
public class UserAgeDescendingComparer : IComparer<User>
{
    public int Compare(User x, User y)
    {
        if (ReferenceEquals(x, y)) return 0;
        if (x is null) return -1;
        if (y is null) return 1;

        // Sıra dəyişir: y.CompareTo(x)
        return y.Age.CompareTo(x.Age);
    }
}

5. Faydalı nüanslar

Bu içəridə necə işləyir?

Sən Sort() metodunu komparatorla çağıranda, siyahı hər elementi bu komparatora ötürür və soruşur: "Hansını qabağa qoyum?". Sənin Compare metodun cavab verir: "bunu, bunu və ya dəyişməyə ehtiyac yoxdur". Sıralama bu dialoqu bütün cütlüklər üçün təkrarlayır, nəticədə sıralanmış siyahı alınır.

Əgər dəyərlər bərabərdirsə nə etməli? Sadəcə 0 qaytar — deməli, elementlərin sırası dəyişməyəcək (və ya daxili sıralama mexanizmləri qərar verəcək).

IComparer<T> daha harada istifadə olunur?

IComparer<T> interfeysi təkcə siyahılarda yox, .NET-də bir çox yerdə aktiv istifadə olunur:

Nümunə:


var sortedSet = new SortedSet<User>(new UserAgeComparer());

İndi SortedSet həmişə avtomatik olaraq yaşa görə sıralanacaq!

Qısa olaraq null təhlükəsizliyi

Yeni başlayanların ən çox rast gəldiyi səhvlərdən biri — NullReferenceException-dır. Compare içində obyektləri null-a yoxlamağı unutma, xüsusən siyahıda belə dəyərlər ola bilərsə.

Tez-tez istifadə olunan pattern (bir daha yazırıq, əmin olmaq üçün):


if (ReferenceEquals(x, y)) return 0;
if (x is null) return -1;
if (y is null) return 1;

Bu yaxşı vərdişdir: proqramın ən lazımsız anda çöküb getməsinin qarşısını alır!

IComparer<T> yanaşmasının üstünlükləri və məhdudiyyətləri

  • Müqayisə məntiqini məlumatdan aydın ayırmaq. İstifadəçi sinifi necə və niyə sıralanacağını düşünməyə məcbur deyil.
  • Müqayisə məntiqini müxtəlif yerlərdə asanlıqla təkrar istifadə etmək.
  • Miqyaslana bilənlik: istənilən qədər sıralama variantı yaratmaq olar, əsas tipi dəyişmədən.

Amma ehtiyatlı ol, "null-u unutdum" və ya "müqayisə məntiqi uyğunsuzdur" vəziyyətinə düşmə. Məsələn, əgər Compare(x, y) 0 qaytarırsa, Compare(y, x) də 0 qaytarmalıdır; əgər Compare(x, y) >0 qaytarırsa, Compare(y, x) <0 qaytarmalıdır və s.

Vizual yaddaş üçün: nə vaxt nə istifadə etməli?

Tapşırıq Nə istifadə etməli Məntiqi harada implementasiya etməli
Bir "təbii" sıralama
IComparable<T>
Tipin özündə (sinif/struktur)
Müxtəlif sıralama variantları
IComparer<T>
Ayrı komparator sinifində
Tez, bir dəfəlik, "yolda"
Comparison<T>
/ lambda
Sort metodunun parametrində, delegatla
Çətin, tez-tez istifadə olunan məntiq
IComparer<T>
Ayrı komparator sinifi kimi

Növbəti mühazirədə sən obyektləri müqayisə etmək üçün delegat və lambda ifadələrindən necə istifadə və kombinə etməyi öyrənəcəksən. Hələlik isə öz tətbiqində bir neçə fərqli komparator implementasiya elə və əsas sinifdən kənarda sıralamanın necə rahat və genişlənə bilən olduğunu hiss et!

6. Komparator implementasiyasında tipik səhvlər

Səhv №1: null yoxlamasının olmaması.
Əgər müqayisədə obyektlərdən biri null-dırsa və sən bunu kodda nəzərə almamısansa, proqram NullReferenceException ilə çökə bilər.

Səhv №2: -1, 0, +1 dəyərlərinin səhv qaytarılması.
Compare metodu birinci obyekt ikinciyə görə kiçikdirsə mənfi, bərabərdirsə sıfır, böyükdürsə müsbət qaytarmalıdır. Bu qaydanı pozsan, sıralama "qəribə" işləyəcək.

Səhv №3: asimmetrik müqayisə məntiqi.
Əgər x və y müqayisəsində bir dəyər, y və x müqayisəsində də eyni dəyəri (əksinə yox) qaytarırsansa, nəticə proqnozlaşdırılmaz olur.

Səhv №4: istifadəçi tipi üçün komparator olmadan Sort() istifadəsi.
Əgər tip IComparable və ya IComparable<T> implementasiya etmirsə, Sort() komparator olmadan çağırılsa, InvalidOperationException atacaq.

Necə qarşısını almaq olar:
Sərhəd halları yoxla, kritik kod hissələrini unit-testlərlə əhatə elə (buna hələ qayıdacağıq!), dokumentasiyaya baxmaqdan çəkinmə — qoy komparatorların işi saat kimi dəqiq olsun.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Sətirlərin uzunluğuna görə müqayisə edən komparatorun reallaşdırılması
Sətirlərin uzunluğuna görə müqayisə edən komparatorun reallaşdırılması
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION