1. Giriş
Niyə comparator-lar həqiqətən vacibdir…
Real layihələrdə hər şey dərs tapşırıqlarındakı kimi sadə olmur: obyektlər mürəkkəbdir, məlumatlar istifadəçilərdən və ya başqa servislərdən gəlir, bizə lazım olan "sıralama qaydaları" (və ya ümumiyyətlə bərabərlik qaydaları) tez-tez tapşırıqdan tapşırığa dəyişir. Comparator-lar kodu çevik edir, davranışı isə proqnozlaşdırılan və idarə olunan hala gətirir.
.NET-də comparator-lar hər dəfə lazım olur ki, istifadəçi class-larının obyektlərini sıralayasan, axtarasan, qruplaşdırasan və ya dublikatları çıxarasan, həmçinin "sıra" olan data strukturları quranda. Xüsusilə bu, kolleksiyalarda, alqoritmlərdə və xarici API-lərlə inteqrasiyada tez-tez rast gəlinir.
.NET-də comparator-lar harada rast gəlinir:
- Kolleksiyaların sıralanması (List<T>.Sort, Array.Sort, OrderBy LINQ-də)
- Sıralı data strukturları (SortedSet<T>, SortedDictionary<TKey, TValue>)
- Kolleksiyalarda obyektlərin axtarışı və müqayisəsi (Contains, IndexOf — "bərabərlik" yox, sıranı müəyyən etmək lazım olanda)
- Qruplaşdırma, filtrasiya və dublikatların çıxarılması (məsələn, .Distinct())
Bu praktikada niyə lazımdır?
- Hesabatlar, harada sıralanmış çıxış vacibdir (məsələn, tələbələrin siyahısı əlifba ilə və ya orta bala görə)
- Gələn datanın etalon dəyərlərlə uyğunlaşdırılması (məsələn, müəyyən kriteriyaya görə yazının axtarışı)
- Yaddaş və sürətə qənaət (düzgün data strukturu seçimi axtarışı sürətləndirir və tətbiqi daha çevik edir)
- Unikallığın yoxlanması (məsələn, istifadəçi qeydiyyatında e-mail üzrə)
2. Həyatdan nümunə: müxtəlif kriteriyalara görə sıralama
Tutaq ki, bizdə istifadəçi class-ı var:
public class User
{
public string FirstName { get; set; } = "";
public string LastName { get; set; } = "";
public string Email { get; set; } = "";
public int Age { get; set; }
// override Equals və GetHashCode əlavə etmək olar — amma bu ev tapşırığı üçün
}
Bizdə istifadəçilərin siyahısı var və istəyirik ki:
- Onları soyadına görə, soyadlar eynidirsə — ada görə sıralayaq.
- İstifadəçini e-mail-ə görə axtarmaq mümkün olsun (registerə fikir vermədən).
- İstifadəçi əlavə edəndə dublikatları çıxaraq.
IComparer<T> ilə sıralama
Variant 1: klassik yol — ayrıca comparator class-ı yaratmaq.
// Soyad və ada görə sıralama üçün comparator
public class UserFullNameComparer : 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;
int lastNameComparison = StringComparer.OrdinalIgnoreCase.Compare(x.LastName, y.LastName);
if (lastNameComparison != 0)
return lastNameComparison;
return StringComparer.OrdinalIgnoreCase.Compare(x.FirstName, y.FirstName);
}
}
İstifadə:
var users = new List<User>
{
new User { FirstName = "İvan", LastName = "Petrov", Age = 20, Email = "ivan.petrov@email.com" },
new User { FirstName = "Anna", LastName = "Smirnova", Age = 22, Email = "anna.smirnova@email.com" },
new User { FirstName = "Petr", LastName = "Petrov", Age = 18, Email = "petr.petrov@email.com" }
};
users.Sort(new UserFullNameComparer());
foreach (var user in users)
{
Console.WriteLine($"{user.LastName} {user.FirstName} ({user.Age})");
}
// Nəticə — istifadəçilər soyadına, soyad eynidirsə — ada görə sıralanır.
Qeyd: Belə edirlər, əgər müqayisə qaydası tətbiqin bir neçə yerində lazımdırsa və lambda artıq copy-paste-dən xilas etmir.
Lambdayla "yolda" sıralama
Bir dəfəlik qayda üçün ayrıca class yazmaq istəmirsən? Lambda kömək edəcək!
users.Sort((x, y) =>
{
int lastNameComparison = StringComparer.OrdinalIgnoreCase.Compare(x.LastName, y.LastName);
if (lastNameComparison != 0)
return lastNameComparison;
return StringComparer.OrdinalIgnoreCase.Compare(x.FirstName, y.FirstName);
});
Eyni cür işləyir, amma comparator birbaşa çağırışda yaradılır. Sətirlərə qənaət edir, amma çox istifadə üçün həmişə rahat deyil.
3. Hiyləgər axtarış: e-mail müqayisəsi registerə fikir vermədən
Real həyatda istifadəçilər e-mail-i necə gəldi yazır, sən isə proqramçısan, hakim yox, ona görə də e-mail-i registerə fikir vermədən müqayisə etmək məntiqlidir.
Bunu comparator və axtarışla reallaşdıraq:
public class EmailComparer : IEqualityComparer<User>
{
public bool Equals(User? x, User? y)
{
if (ReferenceEquals(x, y)) return true;
if (x is null || y is null) return false;
return string.Equals(x.Email, y.Email, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(User obj)
{
return obj.Email?.ToLowerInvariant().GetHashCode() ?? 0;
}
}
HashSet-də istifadə:
var usersSet = new HashSet<User>(new EmailComparer());
usersSet.Add(new User { Email = "Petrov@example.com" });
bool contains = usersSet.Contains(new User { Email = "petrov@example.com" }); // true!
Vacib qeyd: Öz EqualityComparer yazanda, iki metodu da implement etməyi unutma: Equals və GetHashCode. İkinciyi unutdunsa — qəribə davranış olacaq: kolleksiyalarda axtarış "axsayacaq".
4. SortedSet və SortedDictionary-də istifadə
Bax burada comparator-lar özünü tam göstərir.
SortedSet<T> və SortedDictionary<TKey, TValue> sənin obyektlərinlə işləyə bilməz, əgər .NET-ə izah etməsən ki, onları necə müqayisə etmək lazımdır. Üstəlik, müqayisə və bərabərlik qaydası hansı elementlərin fərqli sayılmasına təsir edir!
SortedSet<User> nümunəsi
var sortedUsersByFullName = new SortedSet<User>(new UserFullNameComparer())
{
new User { FirstName = "İvan", LastName = "Petrov", Age = 20 },
new User { FirstName = "Anna", LastName = "Smirnova", Age = 22 },
new User { FirstName = "Petr", LastName = "Petrov", Age = 18 },
new User { FirstName = "İvan", LastName = "Petrov", Age = 25 } // Ad və soyad üzrə dublikat
};
// Müxtəlif yaşda olan "İvan Petrov" — yalnız biri set-ə düşəcək
Console.WriteLine("SortedSet-də istifadəçilər:");
foreach (var user in sortedUsersByFullName)
Console.WriteLine($"{user.LastName} {user.FirstName} ({user.Age})");
SortedSet iki "İvan Petrov" əlavə etməyəcək!
Vacibdir: Burada comparator "unikallıq" məntiqini müəyyən edir. Əgər yalnız ad və soyada görə müqayisə edirsənsə, eyni ad-soyada, amma fərqli yaşda olan istifadəçilər bir istifadəçi sayılır.
5. Cədvəl: .NET-də hansı comparator-u nə vaxt istifadə etməli
| Ssenari | Nə implement etmək lazımdır | İstifadə nümunəsi |
|---|---|---|
| Təbii sıralama qaydası (tip üçün bir, universal) | |
|
| Birkaç sıralama variantı (ada, yaşa, e-mail-ə görə ...) | (class-lar, lambda-lar) |
|
| Kolleksiyada unikal elementlərin axtarışı | |
, |
| Qruplaşdırma, dublikatların silinməsi | |
LINQ |
| Vaxt, tarix və ya mürəkkəb sahə kombinasiyasına görə sıralama | , yolda lambda |
|
| LINQ-də istifadə (bir dəfəlik sorğular) | lambda , |
|
6. Praktika: kolleksiyalarda axtarış və müqayisə
Gəlin tətbiqimizdə registerə fikir vermədən unikal e-mail ilə istifadəçi qeydiyyatını reallaşdıraq. Əgər belə e-mail-li istifadəçi artıq varsa, bunu demək lazımdır.
public static bool RegisterUser(List<User> users, User newUser)
{
// Unikal e-mail axtarışı üçün Any və lambda istifadə edirik
bool exists = users.Any(u =>
string.Equals(u.Email, newUser.Email, StringComparison.OrdinalIgnoreCase));
if (exists)
{
Console.WriteLine($"E-mail ilə istifadəçi: {newUser.Email} artıq qeydiyyatdan keçib!");
return false;
}
users.Add(newUser);
Console.WriteLine($"İstifadəçi {newUser.FirstName} əlavə olundu.");
return true;
}
İstifadə:
var userList = new List<User>
{
new User { Email = "first@example.com" }
};
RegisterUser(userList, new User { FirstName = "Vasya", Email = "FIRST@example.com" });
// Çıxacaq: "E-mail ilə istifadəçi: FIRST@example.com artıq qeydiyyatdan keçib!"
7. Comparator-lardan istifadə zamanı tipik səhvlər
Hamı səhvlər siyahısını sevir, amma biz bunu bir az rəngli edəcəyik.
Bəzən proqramçı istifadəçi tipi üçün comparator yazanda null-u yoxlamamağı unudur və ya — daha pis — müqayisəni elə implement edir ki, "sıra sərtliyi" pozulur. Məsələn, comparator-da ziddiyyətli dəyərlər qaytarırsansa, sıralama gözlənilməz aparacaq, kolleksiyalarda isə obyektlər itəcək və ya "birləşəcək".
Başqa bir tez-tez rast gəlinən anlaşılmazlıq — Equals və comparator məntiqinin uyğunsuzluğudur. Məsələn, Equals obyektləri fərqli sayır, comparator isə eyni, onda SortedSet və ya SortedDictionary-də xaos başlayır: element tapılmır, halbuki orada olmalıdır.
Həmçinin, proqramçı obyektin yalnız bir əlamətini müqayisə edir (məsələn, soyad), amma unudur ki, eyni soyadlı başqa istifadəçilər də ola bilər. Nəticədə obyektlər "silinir", itir, və data artıq sistemin real vəziyyətini əks etdirmir — dublikatlar, lazım olan informasiyanın itməsi və ya proqramın iş məntiqinin pozulması baş verir.
GO TO FULL VERSION