Список List<T>
Чему вы научитесь:
- добавлять, удалять и читать элементы списка
List<T>; - объяснять, чем
Countотличается отCapacityи как растёт список; - понимать, что значит
<T>, и писать свой обобщённый метод; - искать значение по ключу в
Dictionaryи хранить неповторяющиеся значения вHashSet.

Представьте книжную полку. Книги стоят на ней по порядку: новую можно поставить в конец или втиснуть между другими. Когда книгу снимают, соседние сдвигаются. С рядом шкафчиков из темы о массивах так не выйдет: число шкафчиков не меняется.
Зачем программе такая полка? Допустим, вы собираете имена гостей, которые записались на встречу. Сколько их будет, заранее неизвестно. Массив пришлось бы создавать с запасом или каждый раз, когда приходит новый гость, создавать заново, на одно место длиннее.
Для этого в .NET есть список — набор элементов одного типа, в который можно добавлять новые элементы и из которого можно их удалять. Это тип List<T>:
List<string> guests = ["Ann", "Bob"];
guests.Add("Cid");
Console.WriteLine(guests.Count); // 3
Console.WriteLine(guests[2]); // Cid
guests[0] = "Amy";
Console.WriteLine(string.Join(", ", guests)); // Amy, Bob, CidДавайте разберём программу по строкам:
List<string> guestsобъявляет переменную для списка строк. Тип элементов пишут в угловых скобках:List<string>— список строк,List<int>— список чиселint.["Ann", "Bob"]создаёт список из двух элементов. Квадратные скобки со значениями здесь те же, что и у массивов.guests.Add("Cid")ставит новый элемент в конец списка.guests.Count— сколько элементов сейчас в списке:3. У массива этоLength, у списка —Count.guests[2]читает элемент по индексу; индексы, как у массива, начинаются с нуля. ПечатаетсяCid. Квадратные скобки с индексом после имени списка называют индексатором.guests[0] = "Amy";заменяет первый элемент.string.Joinсклеивает элементы списка через запятую, как элементы массива.
Тип List<T> лежит в пространстве имён System.Collections.Generic. Программа .NET 10 подключает это пространство имён сама, поэтому директива using здесь не нужна.
Как создать список
Список из значений
Значения в квадратных скобках вы уже видели у массивов. Объявление слева задаёт тип такой конструкции, поэтому var guests = ["Ann", "Bob"]; не скомпилируется: как и с массивом, это ошибка CS9176. Пустой список записывают как [].
Пустой список через new
new List<int>() создаёт пустой список. Тип здесь виден справа, поэтому переменную можно объявить через var:
var squares = new List<int>();
for (int i = 1; i <= 4; i++)
{
squares.Add(i * i);
}
int sum = 0;
foreach (int s in squares)
{
sum += s;
}
Console.WriteLine(squares.Count); // 4
Console.WriteLine(sum); // 30Цикл for добавляет в список квадраты 1, 4, 9 и 16. Цикл foreach обходит список от первого элемента до последнего, как массив, и складывает элементы: 30.
Вставка и удаление
Список умеет то, чего не умеет массив: менять число элементов. Для этого у него есть три метода:
Insert(index, value)вставляет элемент на местоindex, а элементы с этого места и дальше сдвигает вправо;RemoveAt(index)удаляет элемент по индексу, и все элементы после него сдвигаются влево;Remove(value)удаляет первый элемент, равныйvalue, и возвращаетtrue; если такого элемента нет, возвращаетfalse.
List<string> guests = ["Ann", "Bob", "Cid"];
guests.Insert(1, "Dan");
Console.WriteLine(string.Join(", ", guests)); // Ann, Dan, Bob, Cid
guests.RemoveAt(0);
Console.WriteLine(guests[0]); // Dan
Console.WriteLine(guests.Remove("Bob")); // True
Console.WriteLine(guests.Remove("Zoe")); // False
Console.WriteLine(string.Join(", ", guests)); // Dan, CidРазберём по шагам:
Insert(1, "Dan")ставитDanна место с индексом1;BobиCidсдвигаются вправо.RemoveAt(0)снимаетAnn, и остальные сдвигаются влево:Danбыл вторым, а стал первым.Remove("Bob")находитBob, удаляет его и возвращаетtrue.Zoeв списке нет, поэтомуRemoveничего не удаляет и возвращаетfalse.
Как на полке, индекс элемента — его место сейчас, а не номер навсегда. Если индекс выходит за границу списка, программа останавливается, как и с массивом, но исключение другое: ArgumentOutOfRangeException, а не IndexOutOfRangeException.
Поиск
У списка есть свои IndexOf и Contains. guests.IndexOf("Cid") работает как Array.IndexOf: возвращает индекс первого совпадения или -1, если совпадения нет. guests.Contains("Cid") отвечает, есть ли Cid в списке: true или false.
Список или массив
Здесь полка и список расходятся. Настоящая полка однажды заполняется, и новую книгу поставить некуда. В список же можно добавлять элементы, пока хватает памяти компьютера: место для них список находит сам.
На собеседовании часто спрашивают, чем список отличается от массива. Ответ из двух частей:
- длина массива задаётся при создании и потом не меняется;
- число элементов списка меняется при каждом
Add,Insert,RemoveиRemoveAt.
Частая ошибка
Элемент удалили из списка прямо внутри foreach:
List<int> nums = [1, 2, 3];
foreach (int n in nums)
{
if (n == 2)
{
nums.Remove(n); // ✗
}
}Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute.Программа компилируется, но при запуске останавливается с исключением InvalidOperationException: список изменили во время обхода. foreach этого не допускает, потому что иначе после удаления элементы сдвинулись бы и цикл потерял бы своё место.
Одно известное значение удаляют без цикла: nums.Remove(2);. Чтобы удалить все элементы по условию, обходите список циклом for с конца, от nums.Count - 1 до 0, и удаляйте через RemoveAt(i). Удаление с конца сдвигает только те элементы, которые цикл уже прошёл.
Главное
List<T>— список элементов типаT:AddиInsertдобавляют элементы,RemoveиRemoveAtудаляют.Count— число элементов списка; индексы, как у массива, начинаются с нуля.InsertиRemoveAtсдвигают элементы после указанного места, аRemoveвозвращаетtrueилиfalse.- Пока
foreachобходит список, менять список нельзя: программа остановится с исключениемInvalidOperationException.