Универсальный список List

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

В 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}");
        }
    }
}

Результат работы программы будет следующий:

Capacity = 8
Count = 5
Вася
Петя
Ваня
Гоша
Таня
Второй элемент списка: Петя

Во-первых, стоит обратить внимание на создание списка — здесь мы использовали конструктор с параметром в котором передели начальное значение емкости внутреннего хранилища списка (два элемента). После этого мы добавили в список не два, а пять элементов. Как можно увидеть, никаких ошибок программа не выдала, а в список попали все пять элементов и мы смогли их перечислить в цикле, а второй элемент вывести отдельно. При этом, свойство Capacity также автоматически изменилось и стало равным восьми.

Здесь может возникнуть сразу два закономерных вопроса:

  1. Зачем нам необходимо свойство Capacity у List<T>, если размер списка и так динамически изменяется?
  2. Что произойдет, если установить значение 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;

Как только мы запустим приложение, то сразу же получим исключение:

Unhandled exception. System.ArgumentOutOfRangeException: capacity was less than the current size.

Отсюда следует, что, во-первых, нельзя устанавливать значение 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}");
        }
    }
}

Здесь мы создаем очень-очень большой список строк и наполняем его значениями в цикле, а затем — выводим затраченное время в консоль. Результат работы будет следующим:

На добавление 100000000 элементов затрачено: 11234 мс
Capacity: 134217728

Теперь перепишем нашу программу и при создании списка укажем ему сразу начальную емкость:

На добавление 100000000 элементов затрачено: 10338 мс
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: 134217728
Capacity: 100000000

Итого

Сегодня мы рассмотрели основные моменты по работе со списком List<T> в C#, научились создавать список, выводить значения из списка в консоль, а также разобрались с тем, зачем нам необходимо свойство Capacity и как его использовать.

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
guest
0 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии