В прошлой части мы рассмотрели интерфейс IComparable
, позволяющий сравнивать между собой объекты произвольного типа. Операции сравнения тесно связаны с операцией сортировки различных коллекций. И сегодня мы рассмотрим ещё один интерфейс — IComparer
, который используется для реализации методов сортировки коллекций.
Для чего нужен интерфейс IComparer
Метод List<T>.Sort()
У обобщенного списка List<T>
определено несколько перегруженных методов Sort
, позволяющих производить сортировку элементов списка. В прошлой части мы реализовали интерфейс IComparable
для класса Box
(коробка). Создадим несколько объектов типа Box, поместим их в список и попробуем воспользоваться методом Sort
:
using System.Collections.Generic; List<Box> boxes = new List<Box>() { new Box(){Height = 2, Length = 2, Width = 2 },//Объем 8 new Box(){Height = 1, Length = 1, Width = 1 },//Объем 1 new Box(){Height = 3, Length = 3, Width = 3 },//Объем 27 new Box(){Height = 4, Length = 4, Width = 4 } //Объем 64 }; boxes.Sort(); foreach (Box box in boxes) { Console.WriteLine($"Длина {box.Length} Ширина {box.Width} Высота {box.Height} Объем {box.Volume()}"); }
Так как наш класс реализует интерфейс IComparable
, то платформа .NET «понимает» как сравнивать два объекта типа Box
между собой и, поэтому, результат сортировки будет вполне ожидаемым — все коробки отсортируются по их объему:
Длина 2 Ширина 2 Высота 2 Объем 8
Длина 3 Ширина 3 Высота 3 Объем 27
Длина 4 Ширина 4 Высота 4 Объем 64
Но что, если нам потребуется провести сортировку наших коробок не по объему, а, например, по их длине? Каждый раз переписывать реализацию метода CompareTo
— не вариант. В этом случае нам поможет интерфейс IComparer
, реализуя который в классе мы можем впоследствии переопределять метод сортировки в списке List<T>
(или другой коллекции).
Интерфейс IComparer
Интерфейс IComparer
определяет метод Compare
с помощью которого проводится сравнение двух элементов. Как и в случае с методом CompareTo
, метод Compare
возвращает три значения:
Меньше нуля | Если левый элемент предшествует (меньше) правого, указанного в методе Compare |
Нуль | Если элементы равны |
Больше нуля | Если левый элемент следует за объектом (больше), заданным в методе Compare . |
Попробуем реализовать метод сортировки коробок по их длине. Для этого создадим новый класс BoxComparer
и реализуем в нем интерфейс IComparer
:
internal class BoxComparer : IComparer<Box> { public int Compare(Box? x, Box? y) { if ((x == null) || (y == null)) throw new Exception("Невозможно сравнить элементы"); return x.Length - y.Length; } }
Теперь можем воспользоваться нашим классом-компаратором и отсортировать коробки по длине:
List<Box> boxes = new List<Box>() { new Box(){Height = 3, Length = 2, Width = 8 }, new Box(){Height = 1, Length = 1, Width = 1 }, new Box(){Height = 5, Length = 3, Width = 4 }, new Box(){Height = 1, Length = 4, Width = 1 } }; boxes.Sort(new BoxComparer()); foreach (Box box in boxes) { Console.WriteLine($"Длина {box.Length} Ширина {box.Width} Высота {box.Height} Объем {box.Volume()}"); }
Результат
Длина 2 Ширина 8 Высота 3 Объем 48
Длина 3 Ширина 4 Высота 5 Объем 60
Длина 4 Ширина 1 Высота 1 Объем 4
Используя класс BoxComparer
мы можем реализовать в нем разные методы сортировки, но, при этом, метод Compare
должен быть одним. Например, реализуем сортировку по возрастанию и убыванию длины:
internal class BoxComparer : IComparer<Box> { //Если True - сортируем по возрастанию, иначе - по убыванию public bool AscSort { get; set; } = true public int Compare(Box? x, Box? y) { if ((x == null) || (y == null)) throw new Exception("Невозможно сравнить элементы"); return AscSort ? x.Length - y.Length : y.Length - x.Length; } }
Здесь мы добавили свойство AscSort
, указывающее на то, как сортировать список. Пример использования:
List<Box> boxes = new List<Box>() { new Box(){Height = 3, Length = 2, Width = 8 }, new Box(){Height = 1, Length = 1, Width = 1 }, new Box(){Height = 5, Length = 3, Width = 4 }, new Box(){Height = 1, Length = 4, Width = 1 } }; Console.WriteLine("Сортировка коробок по ВОЗРАСТАНИЮ длины"); boxes.Sort(new BoxComparer()); foreach (Box box in boxes) { Console.WriteLine($"Длина {box.Length} Ширина {box.Width} Высота {box.Height} Объем {box.Volume()}"); } Console.WriteLine("Сортировка коробок по УБЫВАНИЮ длины"); boxes.Sort(new BoxComparer() { AscSort = false}); foreach (Box box in boxes) { Console.WriteLine($"Длина {box.Length} Ширина {box.Width} Высота {box.Height} Объем {box.Volume()}"); }
Результат
Длина 1 Ширина 1 Высота 1 Объем 1
Длина 2 Ширина 8 Высота 3 Объем 48
Длина 3 Ширина 4 Высота 5 Объем 60
Длина 4 Ширина 1 Высота 1 Объем 4
Сортировка коробок по УБЫВАНИЮ длины
Длина 4 Ширина 1 Высота 1 Объем 4
Длина 3 Ширина 4 Высота 5 Объем 60
Длина 2 Ширина 8 Высота 3 Объем 48
Длина 1 Ширина 1 Высота 1 Объем 1
Итого
Интерфейс IComparer
позволяет переопределить способ сортировки элементов коллекции. Для сортировки элементов коллекции, объекты должны реализовывать интерфейс IComparable
, который устанавливает правила сравнения двух элементов (метод CompareTo
). Если у коллекции вызывается метод Sort()
без параметров, то используется метод сравнения по умолчанию, т.е. CompareTo
. Интерфейс же IComparer
позволяет создать свою логику сравнения и сортировки объектов произвольного типа, отличную от реализованной в методе CompareTo
.