Сортировка объектов произвольного типа. Интерфейс IComparer

В прошлой части мы рассмотрели интерфейс 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 между собой и, поэтому, результат сортировки будет вполне ожидаемым — все коробки отсортируются по их объему:

Длина 1 Ширина 1 Высота 1 Объем 1
Длина 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()}");
}

Результат

Длина 1 Ширина 1 Высота 1 Объем 1
Длина 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.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии