Денис Суворов 30 сентября 2022

👨‍🎓️ Учебник по C#: работа с классом List

В статье рассмотрим принципы работы со списком List: создание списка и обращение к данными; добавление, удаление, поиск и сортировка элементов.
👨‍🎓️ Учебник по C#: работа с классом List

Список List<T>

List<T> — класс из пространства имен System.Collections.Generic, список однотипных объектов. В отличие от массива, предоставляет набор методов, облегчающих работу, таких как добавление новых элементов (это удобно, когда неизвестно заранее сколько будет элементов).

Есть несколько вариантов создания списка:

Создание списка без начальных значений

        //List<T> list = new List<T>(); //T - выбранный тип, например int
//Пример:
List<int> list = new List<int>();
//Примеры других типов:
List<string> sList = new List<string>();
List<object> oList = new List<object>();


    

Нет ограничений на тип, который можно поместить в список.

Создание списка с начальными значениями

        //List<T> list = new List<T>(){ Item1, Item2, Item3… }; //Item1/2/3 - элементы нашего списка, тип которых T
//List<T> list = new List<T>{ Item1, Item2, Item3… }; // Тоже допустимо
//Пример:
List<int> list = new List<int> { 1, 2, 3 }; //Мы создали список с набором чисел

    

Создание на основе другого списка

        List<int> list = new List<int> { 1, 2, 3 };
List<int> list2 = new List<int>(list); 

    

Надо заметить, что это не то же самое, что и list2 = list, т. к. при передаче списка в конструктор нового списка создается именно новый список, т. е. в памяти выделяется место для нового объекта, но заполняется теми же значениями, которые были в переданном списке.

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека шарписта»

Комбинированный подход

        List<int> list = new List<int> { 1, 2, 3 };
List<int> list2 = new List<int>(list) { 4 };

    

Если вывести все значения из списка list2, будут выведены значения от 1 до 4.

Также рассмотрим более сложный пример с пользовательским классом.

        class Cup
{
    public string Name { get; set; }
    public int Capacity { get; set; }
    public ConsoleColor Color { get; set; }
    public Cup(string name)
    {
        Name = name;
    }
}
List<Cup> cups = new List<Cup>
{
		new Cup("Моя кружка") {
				Color = ConsoleColor.White,
				Capacity = 1000
		},
		new Cup("Не моя кружка") {
				Color = ConsoleColor.Black,
				Capacity = 300
		}
};

    

Установка начальной емкости списка

Помимо перечисленных выше конструкторов, есть еще один, который принимает размер начальной емкости списка:

        List<int> list = new List<int>(10);

    

Внутри списка находится массив, который при заполнении определенной емкости динамически расширяется, чтобы снизить издержки на выделение памяти при добавлении элементов. На низком уровне при проверке расширения проверяется, если в массиве (который внутри списка) нет элементов, то его размер выставляется равным _defaultCapacity (переменная в исходниках, которая имеет начальное значение = 4), в противном случае считается из «количество элементов» * 2. Т. е. всякий раз, когда мы достигаем определенной границы массива, List автоматически расширяет его в 2 раза, однако ограничение списка — это 2G (Array.MaxArrayLength – 2146435071) элементов. Для повышения производительности, если известен конечный размер списка, можно изначально задать его размер, что избавит от дополнительных выделений памяти.

Обращение к элементам списка

Аналогично как в массиве, допустимо обращаться к элементам списка в квадратных скобках по индексу.

        List<int> list = new List<int>() { 3, 4, 2, 0 };
Console.WriteLine(list[0]);// 3
list[0] = 7;
Console.WriteLine(list[0]);// 7
List<int> list1 = new List<int>();
list1[0] = 1; // Получим исключение ArgumentOutOfRangeException, т.е. для начала нужно создать хоть один элемент списка

    
Доступ к элементам списка
Доступ к элементам списка

Длина списка

Для определения длины списка используется свойство Count.

        List<int> list = new List<int>() { 3, 4, 2, 0 };
Console.WriteLine(list.Count); // 4
list = new List<int>();
Console.WriteLine(list.Count); // 0

    
Получение длины списка
Получение длины списка

Перебор списка

Для перебора списка можно использовать классический цикл, например for.

        List<int> list = new List<int>() { 3, 4, 2, 0 };
for(int i = 0; i < list.Count; i++)
		Console.WriteLine(list[i]);

    
Перебор списка for
Перебор списка for

Также существует специальный цикл для работы со списками foreach.

        List<int> list = new List<int>() { 3, 4, 2, 0 };
foreach(int item in list)
		Console.WriteLine(item);

    
Перебор списка foreach
Перебор списка foreach

Методы списка

Наконец, добрались до самого интересного — методы, которые улучшают работу со списком по сравнении с массивом. Рассмотрим следующие методы (полный список методов можно посмотреть в документации Microsoft):

Добавление в список

  • void Add(T item) — добавляет новый элемент с типом T в конец списка.
  • void AddRange(IEnumerable<T> collection) — добавляет в список коллекцию или массив в конец списка.
  • void Insert(int index, T item) — вставляет элемент в коллекцию по указанному индексу. При выходе за границы коллекции (0 > index или Count-1 < index) будет сформировано исключение System.ArgumentOutOfRangeException.
  • void InsertRange(int index, IEnumerable<T> collection) — аналогично методу выше, только вставляет список элементов по указанному индексу. Также при выходе за границы будет сформировано исключение System.ArgumentOutOfRangeException.

Поиск и проверка элемента

  • int BinarySearch(T item) — бинарный поиск элемента в списке, возвращает индекс элемента. Работает корректно, если список отсортирован. Имеет еще два варианта реализации с дополнительными параметрами для корректного сравнения элементов или указания начальной точки поиска.
  • bool Contains(T item) — проверка наличия элемента в списке.
  • bool Exists(Predicate<T> match) — проверяет наличие в списке хотя бы одного элемента удовлетворяющего условиям делегата match.
  • T Find(Predicate<T> match) — возвращает первый элемент, удовлетворяющий условиям делегата match.
  • List<T> FindAll(Predicate<T> match) — аналогичен Find, только возвращает все элементы.
  • int FindIndex(Predicate<T> match) — возвращает первый индекс элемента, удовлетворяющего условиям делегата match. Имеет еще два варианта реализации с указанием позиции поиска и количества элементов.
  • int FindLastIndex(Predicate<T> match) — аналогично FindIndex, только возвращает индекс последнего элемента, удовлетворяющего условиям делегата match. Также имеет еще два варианта реализации с указанием позиции поиска и количества элементов.
  • List<T> GetRange(int index, int count) — этот метод получает подсписок из списка от указанного индекса с указанным количеством элементов.
  • int IndexOf(T item) — возвращает первый индекс элемента, если он найден в списке либо -1. Также имеет еще два реализации с указанием позиции поиска и количества элементов.
  • int LastIndexOf(T item) — возвращает последний индекс элемента, если он найден в списке либо -1. Также имеет еще два реализации с указанием позиции поиска и количества элементов.

Удаление из списка

  • bool Remove(T item) — удаляет первое вхождение указанного элемента из списка. Возвращает true, если элемент был удален или false, если нет.
  • int RemoveAll(Predicate<T> match) — удаляет все элементы, удовлетворяющие условиям делегата match. Возвращает количество удаленных элементов.
  • void RemoveAt(int index) — удаляет элемент списка с указанным индексом. При выходе индекса за границы списка формирует исключение System.ArgumentOutOfRangeException.
  • void RemoveRange(int index, int count) — удаляет элементы массива с указанного индекса в указанном количестве. Если index меньше 0 или значение параметра count меньше 0, то будет сформировано исключение System.ArgumentOutOfRangeException. Если параметры index и count не указывают допустимый диапазон элементов в списке, будет сформировано исключение System.ArgumentException.
  • void Clear() — удаляет все элементы из списка.

Сортировка списка

  • void Sort() — сортирует элементы. В основе лежит алгоритм быстрой сортировки с ограничением глубины рекурсии до 32, при достижении которого используется сортировка кучей.
  • void Reverse() — изменяет порядок элементов во всем списке на обратный. Имеет перегруженную версию, в которой указывается начальный индекс и количество элементов.

Прочее

  • List<TOutput> ConvertAll<TOutput>(Converter<T, TOutput> converter) — позволяет сконвертировать все типы элементов текущего списка в другой тип. На входе принимает делегат на метод, преобразующий объект от одного типа к другому.
  • void CopyTo(T[] array) — копирует список в массив. Имеет еще 2 варианта реализации с указанием границ и позиций копирования.
  • void ForEach(Action<T> action) — это аналог цикла foreach, только в качестве итерации будет выполняться метод, который на входе принимает элемент с типом T. Еще одно отличие такого цикла от foreach состоит в том, что в методе не сработает ни break, ни continue. Аналогом continue при таком подходе можно считать return.
  • T[] ToArray() — возвращает массив элементов списка.

Примеры

Добавление в список

В этом примере мы создадим массив и разными способами заполним его в цикле.

        List<int> list = new List<int>();
for (int i = 0; i < 10; i++)
{
    list.Add(i); // Добавление элемента в конец списка
    list.AddRange(new[] { i + 4, i + 5 }); // Добавление коллекции элементов в конец списка
    list.Insert(i, i + 1); // Вставка элемента по индексу i
    list.InsertRange(i, new[] { i + 2, i + 3 }); // Вставка массива/коллекции по индексу i
}

    

Удаление из списка

        List<int> list = new List<int>() { 0, 1, 2, 9, 32, -7 }; // Создали массив с элементами
Console.WriteLine(string.Join(",", list));
if (list.Remove(9)) // Удаляем 9
    Console.WriteLine("Элемент 9 успешно удален.");
Console.WriteLine(string.Join(",", list));
list.RemoveAt(0); // Удаляем 0
Console.WriteLine(string.Join(",", list));
list.RemoveAll(i => i < 0); // Удаляем -7
Console.WriteLine(string.Join(",", list));
list.RemoveRange(0, 2); // Удаляес 1 и 2
Console.WriteLine(string.Join(",", list));
list.Clear(); // Удаляем все оставшиеся элементы - 32
Console.WriteLine(string.Join(",", list));

    
Удаление данных из списка
Удаление данных из списка

Поиск и проверка элемента

        List<int> list = new List<int> { 0, 0, 1, -7, 9, 2, 32, 16, 42 };

//Индексы
//Поиск первого четного индекса
Console.WriteLine(list.FindIndex(i => i % 2 == 0)); // 0

//Поиск последнего четного индекса
Console.WriteLine(list.FindLastIndex(i => i % 2 == 0)); // 8

//Поиск индекса по числу из списка
Console.WriteLine(list.IndexOf(-7)); // 3

//Попробуем найти индекс числа которого нет в списке
Console.WriteLine(list.IndexOf(13)); // -1

//Поиск последнего индекса по числу из списка
Console.WriteLine(list.LastIndexOf(0)); // 1

list.Sort(); // Отсортируем массив, так как это основное условие бинарного поиска
Console.WriteLine(list.BinarySearch(9)); // 5

//Проверка вхождения
//Проверим есть ли в списке четные числа
Console.WriteLine(list.Exists(i => i % 2 == 0)); // true

Console.WriteLine(list.Contains(0)); // true
Console.WriteLine(list.Contains(13)); // false

//Значения
//А теперь получим первое четное число
Console.WriteLine(list.Find(i => i % 2 == 0)); // 0

//Найдем все четные числа
Console.WriteLine(string.Join(",", list.FindAll(i => i % 2 == 0))); // 0,0,2,16,32,42

//Последнее четное число
Console.WriteLine(string.Join(",", list.FindLast(i => i % 2 == 0))); // 42

//Получим подсписок, первые 4 элемента
Console.WriteLine(string.Join(",", list.GetRange(0, 4))); // -7,0,0,1

    

Получение диапазона и копирование в массив

        List<int> list = new List<int> { 0, 0, 1, -7, 9, 2, 32, 16, 42 }; 

var range = list.GetRange(2, 3); // Получим диапазон/подсписок
int[] part = new int[range.Count]; // Объявим массив куда будем копировать диапазон
range.CopyTo(part); // Скопируем диапазон

Console.WriteLine(string.Join(",", part)); // 1,-7,9

    
Работа с диапазоном
Работа с диапазоном

Расположение элементов в обратном порядке

        List<int> list = new List<int> { 0, 0, 1, -7, 9, 2, 32, 16, 42 };

list.Reverse();
Console.WriteLine(string.Join(",", list)); // 42,16,32,2,9,-7,1,0,0

list.Reverse(1, 3);
Console.WriteLine(string.Join(",", list)); // 42,2,32,16,9,-7,1,0,0

    
Построение элементов в обратном порядке
Построение элементов в обратном порядке
***

В статье рассмотрена работа с коллекцией List, рассмотрена базовая работа (инициализация, обращение к данными) и методы для работы со списком:

  1. Добавление.
  2. Поиск.
  3. Удаление.
  4. Сортировка.
  5. Работа с диапазонами.

Материалы по теме

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ