Sortarea obiectelor - Interfața IComparable
Majoritatea claselor de colecții încorporate în .NET și array-urile suportă sortarea. Cu ajutorul unei metode, de obicei numită Sort(), poți sorta imediat întregul set de date în ordine crescătoare. De exemplu:
int[] numbers = new int[] { 97, 45, 32, 65, 83, 23, 15 };
Array.Sort(numbers);
foreach (int n in numbers)
Console.WriteLine(n);
// 15 23 32 45 65 83 97
Totuși, metoda Sort funcționează implicit doar pentru seturi de tipuri primitive, cum ar fi int sau string. Pentru a sorta seturi de obiecte complexe, se folosește interfața IComparable. Aceasta are un singur metodă:
public interface IComparable
{
int CompareTo(object? o);
}
Metoda CompareTo este destinată comparării obiectului curent cu obiectul trecut ca parametru object? o. Returnează un număr întreg care poate avea una dintre cele trei valori:
- Mai puțin de zero: Obiectul curent ar trebui să fie înaintea obiectului trecut ca parametru
- Egal cu zero: Ambele obiecte sunt egale
- Mai mare de zero: Obiectul curent ar trebui să fie după obiectul trecut ca parametru
De exemplu, avem clasa Person:
class Person : IComparable
{
public string Name { get; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public int CompareTo(object? o)
{
if(o is Person person) return Name.CompareTo(person.Name);
else throw new ArgumentException("Valoarea parametrului este incorectă");
}
}
Aici, ca criteriu de comparație a fost aleasă proprietatea Name a obiectului Person. Astfel, compararea se face între valoarea proprietății Name a obiectului curent și valoarea proprietății Name a obiectului transmis prin parametru.
Dacă obiectul nu poate fi convertit la tipul Person, se aruncă o excepție.
Utilizare:
var tom = new Person("Tom", 37);
var bob = new Person("Bob", 41);
var sam = new Person("Sam", 25);
Person[] people = { tom, bob, sam };
Array.Sort(people);
foreach (Person person in people)
{
Console.WriteLine($"{person.Name} - {person.Age}");
}
Și în acest caz, obținem următoarea ieșire în consolă:
Bob - 41
Sam - 25
Tom - 37
Interfața IComparable are o versiune generică, astfel că putem simplifica și scurta utilizarea acesteia în clasa Person:
class Person : IComparable<Person>
{
public string Name { get; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public int CompareTo(Person? person)
{
if(person is null) throw new ArgumentException("Valoarea parametrului este incorectă");
return Name.CompareTo(person.Name);
}
}
În mod similar, putem compara după vârstă:
class Person : IComparable<Person>
{
public string Name { get; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public int CompareTo(Person? person)
{
if(person is null) throw new ArgumentException("Valoarea parametrului este incorectă");
return Age - person.Age;
}
}
Utilizarea comparatorului
În afară de interfața IComparable, platforma .NET oferă și interfața IComparer:
public interface IComparer<in T>
{
int Compare(T? x, T? y);
}
Metoda Compare este destinată comparării a două obiecte o1 și o2. Aceasta returnează de asemenea trei valori, în funcție de rezultatul comparației: dacă primul obiect este mai mare decât al doilea, se returnează un număr mai mare decât 0, dacă este mai mic - un număr mai mic decât zero; dacă ambele obiecte sunt egale, se returnează zero.
Să creăm un comparator pentru obiectele Person. Să presupunem că acesta compară obiectele în funcție de lungimea șirului - valoarea proprietății Name:
class PeopleComparer : IComparer<Person>
{
public int Compare(Person? p1, Person? p2)
{
if(p1 is null || p2 is null)
throw new ArgumentException("Valoarea parametrului este incorectă");
return p1.Name.Length - p2.Name.Length;
}
}
class Person
{
public string Name { get; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
În acest caz, se folosește versiunea generică a interfeței IComparer, pentru a evita conversiile inutile de tipuri. Utilizarea comparatorului:
var alice = new Person("Alice", 41);
var tom = new Person("Tom", 37);
var kate = new Person("Kate", 25);
Person[] people = { alice, tom, kate };
Array.Sort(people, new PeopleComparer());
foreach (Person person in people)
{
Console.WriteLine($"{person.Name} - {person.Age}");
}
Comparatorul este specificat ca al doilea parametru al metodei Array.Sort(). În acest caz, nu contează dacă clasa Person implementează interfața IComparable sau nu. Regulile de sortare stabilite de comparator vor avea prioritate.
La început vor fi obiectele Person cu nume mai scurte, iar la final cele cu nume mai lungi:
Tom - 37
Kate - 25
Alice - 41