Большинство встроенных в .NET классов коллекций и массивы поддерживают сортировку. С помощью одного метода, который, как правило, называется
Sort()
можно сразу отсортировать по возрастанию весь набор данных. Например:
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
Однако метод Sort по умолчанию работает только для наборов примитивных типов, как int или string. Для сортировки наборов сложных объектов применяется интерфейс IComparable. Он имеет всего один метод:
public interface IComparable { int CompareTo(object? o); }
Метод CompareTo
предназначен для сравнения текущего объекта с объектом, который передается в качестве параметра object? o
.
На выходе он возвращает целое число, которое может иметь одно из трех значений:
Меньше нуля. Значит, текущий объект должен находиться перед объектом, который передается в качестве параметра
Равен нулю. Значит, оба объекта равны
Больше нуля. Значит, текущий объект должен находиться после объекта, передаваемого в качестве параметра
Например, имеется класс 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("Некорректное значение параметра"); } }
Здесь в качестве критерия сравнения выбрано свойство Name объекта Person. Поэтому при сравнении здесь фактически идет сравнение значения свойства Name текущего объекта и свойства Name объекта, переданного через параметр. Если вдруг объект не удастся привести к типу Person, то выбрасывается исключение.
Применение:
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}"); }
И в данном случае мы получим следующий консольный вывод:
Bob - 41 Sam - 25 Tom - 37
Интерфейс IComparable имеет обобщенную версию, поэтому мы могли бы сократить и упростить его применение в классе 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("Некорректное значение параметра"); return Name.CompareTo(person.Name); } }
Аналогичным образом мы можем сравнивать по возрасту:
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("Некорректное значение параметра"); return Age - person.Age; } }
Кроме интерфейса IComparable платформа .NET также предоставляет интерфейс IComparer:
public interface IComparer<in T> { int Compare(T? x, T? y); }
Метод Compare предназначен для сравнения двух объектов o1 и o2. Он также возвращает три значения, в зависимости от результата сравнения: если первый объект больше второго, то возвращается число больше 0, если меньше - то число меньше нуля; если оба объекта равны, возвращается ноль.
Создадим компаратор объектов Person. Пусть он сравнивает объекты в зависимости от длины строки - значения свойства Name:
class PeopleComparer : IComparer<Person> { public int Compare(Person? p1, Person? p2) { if(p1 is null || p2 is null) throw new ArgumentException("Некорректное значение параметра"); 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; } }
В данном случае используется обобщенная версия интерфейса IComparer, чтобы не делать излишних преобразований типов. Применение компаратора:
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}"); }
Объект компаратора указывается в качестве второго параметра метода Array.Sort()
. При этом не важно, реализует ли класс Person интерфейс
IComparable или нет. Правила сортировки, установленные компаратором, будут иметь больший приоритет. В начале будут идти объекты Person, у которых имена меньше,
а в конце - у которых имена длиннее:
Tom - 37 Kate - 25 Alice - 41