В C# для хранения набора однотипных элементов можно использовать массивы. Однако, что мы будем делать, если заранее не известно какое количество элементов нам необходимо хранить? Или же, количество элементов может постоянно меняться? Каждый раз создавать новый массив и копировать данные из одного массива в другой — не совсем правильный вариант. В таких случаях, наиболее удобно и правильно использовать универсальный список List<T>
.
Класс List<T>
Класс List<T>
представляет собой простейший универсальный список для хранения однородных объектов. Размер List<T>
может динамически изменяться, а доступ к элементам списка осуществляется по целочисленному индексу. Ниже представлены основные свойства и методы List<T>
:
- Метод
void Add(T item)
— добавление нового элемента в список - Метод
void AddRange(ICollection collection)
— добавление в список коллекции или массива - Метод
int BinarySearch(T item)
— бинарный поиск элемента в списке. Если элемент найден, то метод возвращает индекс этого элемента в коллекции. При этом список должен быть отсортирован. - Метод
int IndexOf(T item)
— возвращает индекс первого вхождения элемента в списке - Метод
void Insert(int index, T item)
— вставляет элементitem
в списке на позициюindex
- Метод
bool Remove(T item)
— удаляет элементitem
из списка, и если удаление прошло успешно, то возвращаетtrue
- Метод
void RemoveAt(int index)
— удаление элемента по указанному индексуindex
- Метод
void Sort()
— сортировка списка - Свойство
Capacity
— возвращает или задает общее число элементов, которые может вместить внутренняя структура данных без изменения размера - Свойство
Count
— получает число элементов, содержащихся списке. - Свойство
Item[Int32]
— возвращает или задает элемент по указанному индексу.
Как и в случае с любым универсальным типом, T
— это любой тип данных, который будет использоваться при работе со списком, например, можно создать список строк:
List<string> stringList = List<string>;
Рассмотрим работу со списком List<T>
на примере.
Пример создания и использования списка List<T> в C#
using System; using System.Collections.Generic; namespace ListExample { class Person { public string Name { get; set; } } class Program { static void Main(string[] args) { //при создании списка указываем его начальную емкость (Capacity = 2) 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($"Capacity = {people.Capacity}"); Console.WriteLine($"Count = {people.Count}"); //перебираем элементы в списке foreach (Person person in people) { Console.WriteLine(person.Name); } //получаем конкретный элемент списка по его индексу Console.WriteLine($"Второй элемент списка: {people[1].Name}"); } } }
Результат работы программы будет следующий:
Count = 5
Вася
Петя
Ваня
Гоша
Таня
Второй элемент списка: Петя
Во-первых, стоит обратить внимание на создание списка — здесь мы использовали конструктор с параметром в котором передели начальное значение емкости внутреннего хранилища списка (два элемента). После этого мы добавили в список не два, а пять элементов. Как можно увидеть, никаких ошибок программа не выдала, а в список попали все пять элементов и мы смогли их перечислить в цикле, а второй элемент вывести отдельно. При этом, свойство Capacity
также автоматически изменилось и стало равным восьми.
Здесь может возникнуть сразу два закономерных вопроса:
- Зачем нам необходимо свойство
Capacity
уList<T>
, если размер списка и так динамически изменяется? - Что произойдет, если установить значение
Capacity
меньше, чем текущее количество элементов в списке?
Начнем со второго вопроса. Перепишем нашу программу следующим образом:
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 = "Таня" }); //Устанавливаем новой значение Capacity people.Capacity = 4;
Как только мы запустим приложение, то сразу же получим исключение:
Отсюда следует, что, во-первых, нельзя устанавливать значение Capacity
меньше, чем текущее количество элементов в списке и, во-вторых, для избежания исключительных ситуаций, свойство Capacity
наиболее безопасно устанавливать ДО начала добавления элементов в список.
Что касается того, зачем нам, в принципе, необходимо свойство Capacity
, то здесь нам поможет следующий пример:
using System; using System.Collections.Generic; using System.Diagnostics; namespace ListExample2 { class Program { static void Main(string[] args) { Stopwatch stopwatch = new Stopwatch(); List<string> numbers = new List<string>(); stopwatch.Restart(); for (int i = 0; i < 100000000; i++) numbers.Add(i.ToString()); Console.WriteLine(string.Format("На добавление {0} элементов затрачено: {1:00} мс", numbers.Count, stopwatch.ElapsedMilliseconds)); Console.WriteLine($"Capacity: {numbers.Capacity}"); } } }
Здесь мы создаем очень-очень большой список строк и наполняем его значениями в цикле, а затем — выводим затраченное время в консоль. Результат работы будет следующим:
Capacity: 134217728
Теперь перепишем нашу программу и при создании списка укажем ему сразу начальную емкость:
Capacity: 100000000
Как видно из результатов, даже при использовании списка с достаточно простыми типами данных (в нашем случае — это string
), начальное указание емкости хранилища позволяет сэкономить время работы программы, так как нам уже не потребуется динамически менять размеры внутреннего хранилища списка.
Также стоит отметить, что при динамическом изменении размера списка свойство Capacity
изменяется на значение равному степени двойки, т.е. 2, 4, 8, 16 и т.д, что, опять же приводит к использованию лишней памяти нашей программой. Если вам необходимо, чтобы размер внутреннего хранилища соответствовал реальному количеству элементов в списке, необходимо использовать метод TrimExcess()
, например:
List<string> numbers = new List<string>(); for (int i = 0; i < 100000000; i++) numbers.Add(i.ToString()); Console.WriteLine($"Capacity: {numbers.Capacity}"); numbers.TrimExcess(); Console.WriteLine($"Capacity: {numbers.Capacity}");
Результат работы:
Capacity: 100000000
Итого
Сегодня мы рассмотрели основные моменты по работе со списком List<T>
в C#, научились создавать список, выводить значения из списка в консоль, а также разобрались с тем, зачем нам необходимо свойство Capacity
и как его использовать.