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:
- Ada görə (tələbəni əlifba sırası ilə tez tapmaq üçün).
- Orta bala görə (əlaçılara qrant vermək üçün).
- Yaşa görə (statistika, müsabiqələr və s. üçün).
- 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 |
|---|---|---|---|
|
Birbaşa tipdə (sinif/strukturda) | Bir standart müqayisə üsulu | ID-yə görə artan sıralama |
|
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:
- SortedList<TKey, TValue> və SortedSet<T> kimi kolleksiyaların konstruktorlarında: elementlərin sırasını təyin etmək lazımdır.
- BinarySearch ilə axtarış üçün.
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 | |
Tipin özündə (sinif/struktur) |
| Müxtəlif sıralama variantları | |
Ayrı komparator sinifində |
| Tez, bir dəfəlik, "yolda" | / lambda |
Sort metodunun parametrində, delegatla |
| Çətin, tez-tez istifadə olunan məntiq | |
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.
GO TO FULL VERSION