Menu

List в C#: Add, Remove, Contains, Find и Sort

List<T> это растущий массив в C#. Как создать список, добавлять и вставлять элементы, удалять по значению, индексу или условию, искать через Contains и Find, сортировать по свойству и избежать ошибки при изменении списка внутри foreach.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

List<T> это упорядоченная коллекция, которая растёт и уменьшается по мере добавления и удаления элементов. Она находится в System.Collections.Generic, индексируется как массив (list[0]) и в C# является выбором по умолчанию для «набора чего-то».

Создание списка и добавление элементов

Параметр типа в угловых скобках говорит, что хранит список: List<string>, List<int>, List<Order>.

Вывод:

2
Zoe, Ana, Ben, Chloe, Dev
Ana
Ana B.
3

Count это свойство, а не метод (у массивов Length, в LINQ есть метод Count(); у списка используйте свойство). Insert сдвигает каждый последующий элемент на одну позицию, поэтому вставка в начало большого списка занимает время, пропорциональное его размеру. Дешёвая операция это Add в конец.

Удаление элементов

Есть четыре способа удаления в зависимости от того, что вы знаете об элементе:

Вывод:

True: 85, 62, 40, 91, 55
False
62, 40, 91, 55
2 removed: 62, 91
Caught ArgumentOutOfRangeException
0

Remove принимает значение и удаляет только первое совпадение. Чтобы удалить все копии значения, используйте RemoveAll(s => s == 40). Неверный индекс у списка выбрасывает ArgumentOutOfRangeException (массив вместо этого выбрасывает IndexOutOfRangeException), и для list[i], и для RemoveAt(i). RemoveRange(index, count) удаляет блок.

Count и Capacity

Список хранит элементы во внутреннем массиве. Capacity это размер этого массива; Count это число занятых ячеек. Когда Count достигает Capacity, следующий Add выделяет массив вдвое большего размера и копирует туда всё.

Вывод:

Count 0, Capacity 0
Count 1, Capacity 4
Count 2, Capacity 4
Count 3, Capacity 4
Count 4, Capacity 4
Count 5, Capacity 8
Count 6, Capacity 8
Count 7, Capacity 8
Count 8, Capacity 8
Count 9, Capacity 16
Count 0, Capacity 1000

Благодаря удвоению большинство вызовов Add это просто запись в свободную ячейку. Если вы примерно знаете, сколько элементов будет, передайте число в конструктор, чтобы избежать промежуточных копирований. Учтите, что у new List<int>(1000) всё равно Count равен 0: list[0] на нём выбрасывает исключение. Ёмкость это место, а не элементы.

Поиск: Contains, IndexOf, Find, Exists

Вывод:

True
2
-1
32.00
2 cheap items
False
2
True
False

Find возвращает первое совпадение или значение по умолчанию для типа (null для класса), если ничего не найдено, поэтому проверяйте результат перед использованием. Последняя строка печатает False, потому что Contains использует Equals, а класс без переопределения Equals сравнивает ссылки: новый Product с теми же полями это другой объект. Ищите по свойству через Exists или Find или переопределите в классе Equals и GetHashCode.

Каждый из этих методов проходит список с начала, поэтому каждый вызов занимает O(n). Для повторяющихся поисков по ключу Dictionary отвечает за постоянное время.

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

Sort() сортирует на месте в естественном порядке элементов. Чтобы сортировать по чему-то другому, передайте лямбду сравнения.

Вывод:

1, 2, 5, 9
9, 5, 2, 1
Ben 95, Ana 120, Chloe 150
Chloe 150, Ben 95, Ana 120
Ben 95, Ana 120, Chloe 150

Сравнение возвращает отрицательное число, когда первым должен идти a, положительное, когда b, и ноль при равенстве; CompareTo выдаёт ровно это. На практике важны два различия между Sort и OrderBy из LINQ:

  • Sort меняет список; OrderBy возвращает новую последовательность и не трогает список.
  • Sort не стабилен: равные при сравнении элементы могут поменяться местами. OrderBy стабилен, а ThenBy добавляет второй ключ: players.OrderBy(p => p.Score).ThenBy(p => p.Name).

Вызов Sort() для списка вашего класса без сравнения выбрасывает InvalidOperationException, потому что список не знает, как упорядочивать элементы. Либо передайте сравнение, либо реализуйте в классе IComparable<T>.

Reverse() переворачивает на месте. Поскольку System.Linq тоже определяет расширение Reverse, list.Reverse() у List<T> выбирает версию, работающую на месте, которая ничего не возвращает.

Обход и удаление во время перебора

foreach читает все элементы по порядку. Цикл for даёт ещё и индекс. Нельзя добавлять или удалять элементы посреди foreach по тому же списку:

Вывод:

Caught InvalidOperationException
120, 80, 60
120, 80, 60

Исключение это InvalidOperationException с сообщением «Collection was modified; enumeration operation may not execute.» Прямой цикл for не выбрасывает исключение, но молча пропускает элемент сразу после каждого удалённого, потому что всё сдвигается на один индекс вниз. Обратный обход этого избегает. Третий способ это перебирать копию: foreach (var t in orders.ToList()).

Изменить свойство элемента внутри foreach (например, order.Status = "sent") можно. Добавление или удаление элементов ломает перечислитель, как и замена элемента через индексатор (orders[i] = 0).

Преобразование между списками и массивами

Вывод:

4
3
4, 4, 4, 4
Oslo / Lima / Pune / Kyiv

Оба направления копируют элементы в новое хранилище, поэтому последующее изменение списка не затрагивает массив. ConvertAll это собственная версия списка для Select(...).ToList() из LINQ. Вывод списка через Console.WriteLine(list) показывает имя типа (System.Collections.Generic.List`1[System.String]); используйте string.Join.

Краткая справка

ЗадачаКод
Создатьvar l = new List<int>(); или new List<int> { 1, 2 }
Добавить в конецl.Add(x), l.AddRange(items)
Вставить на позициюl.Insert(i, x)
Удалить первое совпадениеl.Remove(x) (возвращает bool)
Удалить по индексуl.RemoveAt(i)
Удалить по условиюl.RemoveAll(x => ...) (возвращает количество)
Число элементовl.Count
Содержитl.Contains(x), l.Exists(x => ...)
Найтиl.Find(...), l.FindAll(...), l.FindIndex(...)
Сортировать на местеl.Sort(), l.Sort((a, b) => ...)
Отсортированная копияl.OrderBy(x => ...).ToList()
В массивl.ToArray()

Частые ошибки

  • Удаление внутри foreach. Выбрасывает InvalidOperationException; используйте RemoveAll или обратный цикл for.
  • Ожидание, что Remove(x) удалит все копии. Он удаляет первую; используйте RemoveAll.
  • Contains для объектов без Equals. Сравниваются ссылки; ищите по свойству через Exists.
  • Обращение по индексу к списку, созданному с ёмкостью. new List<int>(10) пуст; сначала добавьте элементы.
  • Ожидание, что Sort сохранит порядок равных элементов. Не сохранит; используйте OrderBy, когда порядок среди равных важен.
  • Присваивание списка, чтобы поделиться им, и удивление от изменений. var copy = list; это тот же список; new List<T>(list) это копия.

Часто задаваемые вопросы

Как добавить элементы в List в C#?

list.Add(item) добавляет один элемент в конец, list.AddRange(otherCollection) добавляет много, а list.Insert(index, item) помещает элемент на позицию и сдвигает остальные. Список можно заполнить и при создании: var names = new List<string> { "Ana", "Ben" };.

Как удалить элемент из List в C#?

Remove(value) удаляет первый совпавший элемент и возвращает true, если нашёл его. RemoveAt(index) удаляет по позиции. RemoveAll(x => condition) удаляет все элементы, удовлетворяющие условию, и возвращает их количество. Clear() очищает список.

Как проверить, содержит ли List значение, в C#?

list.Contains(value) возвращает true или false. Он сравнивает через Equals, поэтому для ваших классов сравниваются ссылки, если класс не переопределяет Equals. Чтобы проверить по свойству, используйте list.Exists(p => p.Name == "Ana") или list.Any(...) из LINQ.

Как отсортировать List по свойству в C#?

Передайте сравнение в Sort: people.Sort((a, b) => a.Age.CompareTo(b.Age)) сортирует на месте. Поменяйте a и b местами для сортировки по убыванию. Или используйте LINQ, который возвращает новую отсортированную последовательность и сохраняет порядок равных элементов: people.OrderBy(p => p.Age).ToList().

Почему возникает «Collection was modified; enumeration operation may not execute»?

Вы добавили, удалили или заменили (list[i] = x) элементы, пока foreach перебирал тот же список, и перечислитель выбросил InvalidOperationException на следующем шаге. Используйте list.RemoveAll(condition), обратный цикл for или перебирайте копию, например list.ToList().

Чем Count отличается от Capacity?

Count это количество элементов в списке. Capacity это размер внутреннего массива, который не меньше Count. Когда массив заполняется, список выделяет новый вдвое большего размера и копирует элементы, поэтому Add в среднем быстрый.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ