Содержание
В предыдущей части мы познакомились с тем, что из себя представляет универсальный список List<T>, научились создавать такие списки и выводить значения различных элементов списка в консоль. Сегодня разберемся с тем, как сортировать списки List<T>, содержащие различные типы элементов.
Как мы уже знаем, в классе List<T> предусмотрен метод Sort(), который производит сортировку элементов списка с использованием компаратора по умолчанию. Что это означает рассмотрим на примере.
Пример использования метода Sort() для простых типов данных
Создадим список строк и попробуем его отсортировать:
List<string> list = new List<string>();
list.Add("Вася");
list.Add("Петя");
list.Add("Ваня");
list.Add("Гоша");
list.Add("Таня");
Console.WriteLine("Несортированный список");
foreach (string str in list)
Console.WriteLine(str);
Console.WriteLine("Отсортированный список");
list.Sort();
foreach (string str in list)
Console.WriteLine(str);
Результат выполнения программы будет следующим:
Петя
Ваня
Гоша
Таня
Cортированный список
Ваня
Вася
Гоша
Петя
Таня
То есть мы получили ровно то, что и ожидали — все строки с именами отсортировались по алфавиту. Аналогичным образом, метод Sort() сработает и на числах. Однако, если мы попробуем выполнить вот такой код:
using System;
using System.Collections.Generic;
namespace ListExample3
{
class Person
{
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Person> people = new List<Person>(2);
people.Add(new Person() { Name = "Вася" });
people.Add(new Person() { Name = "Петя" });
people.Add(new Person() { Name = "Ваня" });
people.Add(new Person() { Name = "Гоша" });
people.Add(new Person() { Name = "Таня" });
Console.WriteLine("Несортированный список");
foreach (Person person in people)
{
Console.WriteLine(person.Name);
}
people.Sort();
Console.WriteLine("Cортированный список");
foreach (Person person in people)
{
Console.WriteLine(person.Name);
}
}
}
}
то гарантированно получим исключение следующего содержания:
IComparableДля элементов списка не был обнаружен компаратор по умолчанию в результате чего мы и получили сообщение об исключении в котором говориться, что хотя бы один элемент списка должен реализовывать интерфейс IComparable.
Пример использования метода Sort() с компаратором по умолчанию для объектов
Попробуем переписать наш предыдущий пример следующим образом:
using System;
using System.Collections.Generic;
namespace ListExample3
{
class Person : IComparable
{
public string Name { get; set; }
public int CompareTo(object? obj)
{
if ((obj == null) || (!(obj is Person)))
return 0;
else
return string.Compare(Name, ((Person)obj).Name);
}
}
class Program
{
static void Main(string[] args)
{
List<Person> people = new List<Person>(2);
people.Add(new Person() { Name = "Вася" });
people.Add(new Person() { Name = "Петя" });
people.Add(new Person() { Name = "Ваня" });
people.Add(new Person() { Name = "Гоша" });
people.Add(new Person() { Name = "Таня" });
Console.WriteLine("Несортированный список");
foreach (Person person in people)
{
Console.WriteLine(person.Name);
}
people.Sort();
Console.WriteLine("Cортированный список");
foreach (Person person in people)
{
Console.WriteLine(person.Name);
}
}
}
}
Здесь наш класс Person реализует интерфейс IComparable и, соответственно, содержит компаратор по умолчанию — метод CompareTo, который возвращает целочисленное значение: -1, 0 или 1. Смысл работы этого метода точно такой же, как и метода Compare у строк. Теперь, когда мы реализовали в классе компаратор по умолчанию, метод Sort у списка будет работать без вызова исключений, а результат будет такой же, как и при сортировке строк.
В методе CompareTo можно реализовывать любую логику сравнения двух объектов — сравнивать объекты не по одному, а по нескольким полям, делать сортировку по возрастанию или убыванию и т.д.
Пример использования перегруженного метода Sort()
Несмотря на то, что реализация интерфейса IComparable классом позволяет создавать любую логику сравнения элементов списка, иногда бывает необходимым производить сортировку иным образом, чем это реализовано в компараторе по умолчанию. И тогда нам на помощь может прийти перегруженный метод Sort(), который в качестве параметра принимает метод реализующий функцию сравнения двух элементов списка. Например, перепишем нашу программы таким образом, чтобы у класса Person появилось поле возраста человека и отсортируем список по этому полю:
using System;
using System.Collections.Generic;
namespace ListExample3
{
class Person : IComparable
{
public string Name { get; set; }
public byte Age { get; set; }
public int CompareTo(object? obj)
{
if ((obj == null) || (!(obj is Person)))
return 0;
else
return string.Compare(Name, ((Person)obj).Name);
}
}
class Program
{
static void Main(string[] args)
{
List<Person> people = new List<Person>(2);
people.Add(new Person() { Name = "Вася", Age = 10 });
people.Add(new Person() { Name = "Петя", Age = 3 });
people.Add(new Person() { Name = "Ваня", Age = 6 });
people.Add(new Person() { Name = "Гоша", Age = 18 });
people.Add(new Person() { Name = "Таня", Age = 7 });
Console.WriteLine("Несортированный список");
foreach (Person person in people)
{
Console.WriteLine(person.Name);
}
people.Sort();
Console.WriteLine("Список отсортированный по имени");
foreach (Person person in people)
{
Console.WriteLine($"{person.Name} возраст {person.Age}");
}
people.Sort(delegate (Person x, Person y)
{
if (x == null && y == null) return 0;
else if (x == null) return -1;
else if (y == null) return 1;
else
return x.Age.CompareTo(y.Age);
});
Console.WriteLine("Список отсортированный по возрасту");
foreach (Person person in people)
{
Console.WriteLine($"{person.Name} возраст {person.Age}");
}
}
}
}
При сортировке списка по возрасту мы передали в метод Sort() анонимный метод в котором и произвели сортировку элементов списка по необходимому нам полю, не переписывая при этом компаратор по умолчанию, реализованный в самом классе Person. Результат работы программы будет следующий:
Вася
Петя
Ваня
Гоша
Таня
Список отсортированный по имени
Ваня возраст 6
Вася возраст 10
Гоша возраст 18
Петя возраст 3
Таня возраст 7
Список отсортированный по возрасту
Петя возраст 3
Ваня возраст 6
Таня возраст 7
Вася возраст 10
Гоша возраст 18
Итого
Сегодня мы научились производить сортировку списков, содержащих различные типы элементов. Реализовали интерфейс IComparable в своем классе и создали компаратор по умолчанию, а также отсортировали список List<T>, используя анонимный метод в качестве параметра метода Sort.