1. Введение
Представьте, что вы ведете список покупок. Сегодня вам нужно купить 3 товара, завтра — 10, а послезавтра — всего один. Если бы вы использовали массив, то пришлось бы каждый раз создавать новый массив под конкретное количество товаров. Или создавать очень большой массив "на всякий случай", тем самым "носить" с собой уйму пустых ячеек. Звучит не очень эффективно, верно?
А теперь представьте, что у вас есть волшебный рюкзак, который сам расширяется, когда вы добавляете туда вещи, и сжимается, когда вы их вынимаете. Вам не нужно заранее знать, сколько вещей вы положите. Вы просто кладете, и рюкзак адаптируется. Вот это и есть List<T> в мире программирования на C#!
List<T> — это обобщенная коллекция, которая позволяет хранить последовательность элементов определенного типа. Ключевое слово здесь — "динамический". В отличие от массивов, List<T> может изменять свой размер во время выполнения программы. Он сам управляет своей внутренней емкостью, увеличивая ее при необходимости.
Что означает буква T в List<T>? Это так называемый параметр типа. Когда вы создаете List, вы указываете, какой тип данных он будет хранить вместо T. Например, List<int> будет хранить только целые числа, List<string> — только строки, а List<double> — только вещественные числа. Это гарантирует, что вы случайно не сможете добавить яблоко в список апельсинов, что очень важно для стабильности и предсказуемости вашего кода.
Зачем это нужно?
В реальных проектах очень часто встречается ситуация, когда вы заранее не знаете, сколько элементов у вас будет. Например:
- Список пользователей, зашедших на сайт.
- Список товаров в корзине интернет-магазина.
- Список результатов поиска по запросу.
- Список задач в планировщике, который мы сейчас и будем разрабатывать.
Во всех этих случаях количество элементов может меняться, и List<T> становится нашим незаменимым помощником.
Основные методы List<T>
| Метод | Описание | Пример |
|---|---|---|
|
Добавить элемент в конец списка | |
|
Вставить на заданную позицию | |
|
Удалить первое вхождение элемента | |
|
Удалить элемент по индексу | |
|
Очистить список | |
|
Проверить, есть ли в списке элемент | |
|
Индекс первого вхождения | |
|
Количество элементов в списке | |
|
Ёмкость внутреннего буфера | |
2. Создание списка (List<T>)
Приступим к практике! Представьте, что мы решили создать простенький менеджер задач. Нам нужен список, куда мы будем добавлять наши дела.
Для создания нового списка List<T> мы используем оператор new, точно так же, как и при создании других объектов.
Как объявить и создать List
Синтаксис создания списка очень похож на массив, только вместо квадратных скобок — угловые:
using System.Collections.Generic;
List<int> numbers = new List<int>();
Комментарии для тех, кто любит подробности:
- List<int> — это список целых чисел (int). Вместо int можно подставить любой другой тип: string, double, собственный класс и так далее.
- После создания список пуст — его длина равна 0.
Пример: первый шаг в развитии нашего приложения
Допустим, у нас есть приложение, в котором пользователь вводил имя и возраст (ещё с первых лекций). Давайте теперь добавим возможность сохранять несколько имён в список, чтобы потом с ними работать.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> names = new List<string>();
Console.WriteLine("Введите три имени:");
for (int i = 0; i < 3; i++)
{
string name = Console.ReadLine();
names.Add(name);
}
Console.WriteLine("Вы ввели:");
foreach (string n in names)
{
Console.WriteLine(n);
}
}
}
Обратите внимание на Add(). Именно этот метод добавляет в список новые элементы. По сути — делает его динамическим!
Можно создать список сразу с элементами!
List<string> planets = new List<string> { "Меркурий", "Венера", "Земля" };
Очень удобно, когда вы знаете "стартовый состав". Такой синтаксис часто применяют и в тестах, и при инициализации фиксированных данных.
3. Базовые операции с List<T>
Прежде чем мы перейдём к трюкам и магии, давайте разберём "азбуку" списка.
Добавление элементов: Add()
Использовать метод Add() — как положить новую вещь на полку:
List<int> numbers = new List<int>();
numbers.Add(42);
numbers.Add(100);
Теперь внутри: [42, 100]
Получение количества элементов: Count
В отличие от массива, у списка свойство называется не Length, а Count:
int count = numbers.Count; // Вернёт 2
Помните: у списков используется свойство Count, а у массивов и строк — Length. Это разные свойства для похожей цели.
Доступ к элементам по индексу
Два способа, оба идентичны по сути:
int first = numbers[0]; // 42
numbers[1] = 200; // Заменили 100 на 200
Важно: Индексация, как и у массивов, начинается с нуля. Не пытайтесь обратиться к numbers[3], если у вас всего три элемента!
Перебор элементов списка
Можно использовать цикл for или foreach:
// Через for — если нужен индекс
for (int i = 0; i < numbers.Count; i++)
{
Console.WriteLine(numbers[i]);
}
// Через foreach — если индекс не нужен
foreach (int number in numbers)
{
Console.WriteLine(number);
}
foreach обычно проще и понятнее, особенно для больших коллекций.
Но с ним вы не можете изменить элементы коллекции по индексу или досрочно выйти из цикла с помощью break, если не используете дополнительные конструкции.
Вставка элемента по индексу: Insert()
Метод Insert() нужен, если вы захотите добавить элемент не в конец списка, а, куда угодно. Например, на второе место:
numbers.Insert(1, 55); // Вставит 55 на позицию с индексом 1
Теперь numbers: [42, 55, 200]
Удаление элементов
Удалять из списка почти так же просто, как добавлять в него:
numbers.Remove(55); // Удалит ПЕРВОЕ вхождение 55
numbers.RemoveAt(0); // Удалит элемент с индексом 0
numbers.Clear(); // Полностью очищает список
Нюансы удаления:
- Если удалить несуществующее значение методом Remove, ничего плохого не случится: список не изменится, ошибок вы не получите.
- После Clear() список снова пуст.
4. Полезные нюансы
Как растёт List<T>
Когда вы добавляете элементы в List<T>, он не увеличивает размер каждый раз понемногу. Вместо этого List<T> заранее выделяет блок памяти с запасом (внутренняя ёмкость — Capacity).
Как только элементов становится больше, чем текущая ёмкость, список выделяет новый, более крупный блок памяти (обычно в 2 раза больше) и копирует туда все старые значения:
Добавляем элементы → вместилось → вместилось → вместилось →
Новый элемент не вмещается → создаётся массив побольше → копируются данные → продолжаем
Это позволяет добавлять элементы быстрее, ведь память перераспределяется не при каждом Add, а лишь изредка.
var list = new List<int>(); // Capacity = 0
list.Add(1); // Capacity = 4
list.Add(2); // Capacity = 4
list.Add(3); // Capacity = 4
list.Add(4); // Capacity = 4
list.Add(5); // Capacity = 8 (расширение!)
Capacity vs. Count: в чём разница?
- Count — количество элементов, добавленных в список.
- Capacity — размер внутреннего массива, хранящего элементы.
List<int> nums = new List<int>();
Console.WriteLine(nums.Count); // 0
Console.WriteLine(nums.Capacity); // Обычно 0 или небольшой размер
nums.Add(123);
Console.WriteLine(nums.Count); // 1
Console.WriteLine(nums.Capacity); // Теперь может быть больше Count
nums.Capacity = 1000; // Можно явно увеличить Capacity, если знаете, что будет много элементов
Большинство разработчиков используют только Count, но если вы пишете требовательные к производительности приложения (например, игры или анализ данных), то тонкая настройка Capacity может быть полезной.
5. Пример
Продолжим развивать наше маленькое приложение — теперь, например, пользователь может добавлять свои любимые числа, а затем работать со списком: вставлять, удалять, выводить и очищать.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<int> favoriteNumbers = new List<int>();
Console.WriteLine("Введите ваши любимые числа (по одному в строке, 0 — завершить):");
while (true)
{
int num = int.Parse(Console.ReadLine());
if (num == 0) break;
favoriteNumbers.Add(num);
}
Console.WriteLine("Ваши любимые числа:");
foreach (int n in favoriteNumbers)
{
Console.Write(n + " ");
}
Console.WriteLine("\nХотите удалить первое число? (y/n)");
if (Console.ReadLine().ToLower() == "y")
{
favoriteNumbers.RemoveAt(0);
}
Console.WriteLine("А теперь ваш список:");
foreach (int n in favoriteNumbers)
{
Console.Write(n + " ");
}
}
}
Замечание: Здесь мы используем int.Parse. В реальных проектах лучше всегда проверять ввод на корректность через int.TryParse, чтобы не "ронять" программу на любой ошибке пользователя.
6. Частые ошибки и "грабли" начинающих
Очень популярен такой сценарий: вы пишете цикл от 0 до List.Count, но в процессе цикла (например, в foreach) меняете сам список (добавляете или удаляете элементы). Не делайте так! Элита программирования называет это "модификация коллекции во время перебора". В большинстве случаев вы сразу получите исключение типа InvalidOperationException.
Пример плохого кода:
foreach (int n in numbers)
{
if (n < 0)
numbers.Remove(n); // БУМ! Runtime error.
}
Правильно: или сначала собрать индексы/элементы для удаления в отдельный список, или воспользоваться циклом for с обратным направлением.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ