1. Giriş
Təsəvvür elə, dostundan kitabları rəflərə düzməsini xahiş edirsən. Əgər kitabların üzərində nömrələr varsa (1, 2, 3…), dostun bunu rahatlıqla edəcək: "Aha, 1 2-dən əvvəl gəlir, 2 3-dən əvvəl gəlir". Bu — ədədlərin təbii qaydasıdır (ardıcıllıqla). Eyni qayda ilə, əgər kitabların adları "A", "B", "V" hərfləri ilə başlayırsa, onları əlifba sırası ilə düzəcək. Bu da sətirlər üçün "təbii" qaydadır (əlifba ilə).
Bəs əgər kitabların üzərində sadəcə müəllifin soyadı yazılıbsa? Və sən deyirsən: "Qaydaya görə düz". Dostun soruşacaq: "Hansına görə? Soyadına? Çap ilinə? Səhifə sayına?" Bax, problem də burdan başlayır. Sənin unikal obyektlərin üçün aşkar təbii qayda yoxdur.
Proqramlaşdırmada da eyni məsələdir. Məsələn, tam ədədlər siyahısını List<int> sıralamaq istəyəndə, C# bunu necə edəcəyini yaxşı bilir. List<string> üçün də əlifba qaydasından istifadə edir. Amma əgər List<Student> varsa, burada hər Student — ad, soyad, yaş, ID və bir sürü başqa şey saxlayırsa, C# çaşır. Hansı xüsusiyyətə görə müqayisə etsin? Ada? ID-yə? Orta bala? Bu, "Kolleksiyaların sıralanması" mövzusunda rastlaşdığımız problemdir: List<T>.Sort() istifadə edəndə, öz obyektlərimizi sıralamaq istəyəndə səhv çıxır.
Bu tapmacanı həll etmək üçün C#-a aydın şəkildə deməliyik ki, obyektlərimizi hansı prinsipə görə müqayisə etmək lazımdır. Bunun üçün IComparable<T> interfeysi var.
2. IComparable<T> interfeysi
Deməli, obyektlərimizi "öz yerini" bilən etmək üçün IComparable<T> interfeysindən istifadə edirik. Bu — müqavilədir. Sənin class-ın və ya struct-ın bu interfeysi reallaşdıranda, sanki kompilyatora deyirsən: "Salam! Mənim obyektlərim bir-biri ilə müqayisə oluna bilər və bax belə etmək lazımdır."
Necə işləyir?
IComparable<T> interfeysi cəmi bir metod təyin edir:
public interface IComparable<in T>
{
int CompareTo(T other);
}
Bu metod T tipində obyekt qəbul edir (yəni cari obyektin tipində) və qaytarmalıdır:
- mənfi ədəd (< 0), əgər cari obyekt "kiçikdirsə";
- sıfır (0), əgər "bərabərdirsə" (sıralama baxımından);
- müsbət ədəd (> 0), əgər cari obyekt "böyükdürsə".
Əslində, bu idman hakimlərinə bənzəyir: məsələn, boks döyüşündə — əgər sən daha yaxşı idin, raundu udursan və daha çox xal alırsan; zəif idinsə — az xal; bərabərdirsə — xal eynidir. Burada isə hakimlər əvəzinə sadəcə işarəli ədəd var.
Niyə məhz belə?
Sıralama metodları (məsələn, List<T>.Sort()) siyahıdakı elementlər üçün CompareTo çağırır ki, hansını qabağa, hansını axıra qoymaq lazım olduğunu bilsin. Əgər class-ın bu interfeysi reallaşdırırsa — onu sıralamaq olar!
3. Praktika
Tutaq ki, belə bir istifadəçi class-ımız var (User):
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
Gəlin istifadəçilər siyahısını sıralamağa çalışaq:
List<User> users = new List<User>
{
new User { Name = "Sergey", Age = 31 },
new User { Name = "Mariya", Age = 22 },
new User { Name = "Anton", Age = 27 }
};
users.Sort(); // BOOM! InvalidOperationException
Səhv çıxır: "Ən azı bir obyekt IComparable reallaşdırmalıdır" (Ən azı bir obyekt IComparable reallaşdırmalıdır).
Düzəldirik: IComparable<User> reallaşdırırıq
Class-ımıza interfeys əlavə edirik. Gəlin əvvəlcə yaşa görə sıralayaq — kiçikdən böyüyə:
public class User : IComparable<User>
{
public string Name { get; set; }
public int Age { get; set; }
public int CompareTo(User other)
{
// Axmaqlıqdan qorunma: əgər other == null, bizim istifadəçi "böyükdür"
if (other == null) return 1;
return this.Age.CompareTo(other.Age); // yaşa görə sıralayırıq
}
}
İndi siyahı düzəldək, users.Sort(); çağırıb nəticəni konsolda çap edək:
List<User> users = new List<User>
{
new User { Name = "Sergey", Age = 31 },
new User { Name = "Mariya", Age = 22 },
new User { Name = "Anton", Age = 27 }
};
// Yaşa görə sıralama (CompareTo istifadə edir)
users.Sort();
// Sıralanmış istifadəçiləri çıxışa veririk
foreach (User user in users)
{
Console.WriteLine($"{user.Name}, {user.Age}");
}
Siyahı yaşa görə sıralanacaq:
Mariya, 22
Anton, 27
Sergey, 31
Vizualizasiya: əvvəl və sonra
| Ad | Yaş |
|---|---|
| Sergey | 31 |
| Mariya | 22 |
| Anton | 27 |
Sıralamadan sonra:
| Ad | Yaş |
|---|---|
| Mariya | 22 |
| Anton | 27 |
| Sergey | 31 |
4. Vacib detallar və tez-tez edilən səhvlər
null-dan qorunma
CompareTo içində çox vacibdir ki, other null-a bərabər deyilmi, yoxlayasan. Əgər bunu unutsaq, obyekt NullReferenceException ala bilər — onlar da kodda buraxılan bug-lar kimi hiyləgərdir. Adətən, müqayisə olunan obyekt null olsa, cari obyekt "böyük" sayılır:
public int CompareTo(User other)
{
if (other == null) return 1;
// ...
}
Tranzitivliyi qoruyun
Əgər A < B və B < C-dirsə, onda A < C olmalıdır. Əgər bu qaydaya əməl etməsən, sıralama gözlənilməz davranacaq (yəni, maraqlı, amma səhv!).
Əgər bir neçə sahəyə görə sıralamaq lazımdırsa
Tutaq ki, əvvəlcə yaşa görə, amma iki istifadəçi eyni yaşdadırsa, onları ada görə əlifba ilə sıralamaq lazımdır. Bu belə edilir:
public int CompareTo(User other)
{
if (other == null) return 1;
int ageCompare = this.Age.CompareTo(other.Age);
if (ageCompare != 0) return ageCompare;
// Əgər yaşlar bərabərdirsə — ada görə müqayisə edirik
return this.Name.CompareTo(other.Name);
}
5. Sıralama: artıq sənin obyektlərinlə də
List<int> nə bacarırsa — sənin class-ın da bacarır
İndi müqayisə tələb edən istənilən metodu istifadə edə bilərsən: Sort, BinarySearch, hətta sıralanmış kolleksiyalara əlavə etmə (məsələn, SortedSet<T>).
users.Sort();
// users artıq yaşa görə (və yaş bərabərdirsə ada görə) sıralanıb
Kurs tətbiqində nümunə
Tutaq ki, əvvəllər istifadəçi uçotu üçün tətbiq yazmışdın. İndi onlara "təbii" sıralama qaydası əlavə edə bilərik. Bax belə görünəcək:
// Bizim User class-ı artıq IComparable<User> reallaşdırır
List<User> users = new List<User>
{
new User { Name = "İvan", Age = 45 },
new User { Name = "Qalina", Age = 27 },
new User { Name = "Yuri", Age = 27 }
};
// Əvvəl yaşa, sonra ada görə sıralayırıq
users.Sort();
foreach (var u in users)
{
Console.WriteLine($"{u.Name} - {u.Age}");
}
Nəticə:
Qalina - 27
Yuri - 27
İvan - 45
Qalina və Yuri — eyni yaşdadır, yaş bərabərdirsə ada görə sıralanır.
6. CompareTo necə işləyir: sərt riyaziyyat
Gəlin bir daha qaytarılan dəyərlərin standartına diqqət yetirək:
- Mənfi ədəd (məsələn, -1): cari obyekt müqayisə olunanın qabağında gedir.
- Sıfır: sıralama baxımından bərabər sayılırlar.
- Müsbət ədəd (məsələn, 1): cari obyekt müqayisə olunanın arxasında gedir.
Daxili tiplər (məsələn, Age.CompareTo(other.Age)) artıq bu standartı reallaşdırır, həmişə -1, 0 və ya 1 qaytarır.
CompareTo metodu üçün qaytarma cədvəli
| Qaytarılan dəyər | Nə deməkdir? | Nümunə |
|---|---|---|
| < 0 | Kiçikdir (qabaqda gedir) | |
| 0 | Bərabərdir | |
| > 0 | Böyükdür (sonra gedir) | |
7. Çoxsahəli sıralama: sahələri birləşdiririk
Bəzən daha mürəkkəb sıralama lazımdır: məsələn, soyad, ad və yaşa görə. Artıq tanış olduğumuz üsulla, ardıcıl müqayisə edə bilərik:
public class Student : IComparable<Student>
{
public string LastName { get; set; }
public string FirstName { get; set; }
public int Grade { get; set; }
public int CompareTo(Student other)
{
if (other == null) return 1;
int lastNameCompare = this.LastName.CompareTo(other.LastName);
if (lastNameCompare != 0) return lastNameCompare;
int firstNameCompare = this.FirstName.CompareTo(other.FirstName);
if (firstNameCompare != 0) return firstNameCompare;
return this.Grade.CompareTo(other.Grade);
}
}
8. IComparable<T> reallaşdırmaq lazım olmayan hallar
Həyatdan (proqramçı həyatından!) bir vəziyyət: əgər obyektin "təbii" sıralama qaydası yoxdursa, IComparable<T> reallaşdırmaq yaxşı deyil. Məsələn, Point class-ın varsa, amma bilmirik — X-ə, Y-yə, yoxsa koordinat başlanğıcından məsafəyə görə sıralamaq lazımdır — onda sıralama xaricdən, müqayisə funksiyası ilə (IComparer<T>) verilsin. Bu barədə növbəti mühazirədə danışacağıq.
GO TO FULL VERSION