Урок 5.2 — Список (List)
Динамический массив: добавление, удаление, поиск. Главная коллекция C#.
Массив — это здорово, но у него есть серьёзный недостаток: размер фиксирован. А что если ты не знаешь заранее, сколько элементов понадобится? На помощь приходит List<T> — динамический список из пространства имён System.Collections.Generic. Он как массив, но может автоматически расширяться при добавлении новых элементов.
Создание списка List<T>
List<T> — это обобщённый (generic) тип. Буква T заменяется на конкретный тип элементов: int, string, double и любой другой.
Список — это ссылочный тип. Он хранится в куче (heap), а переменная содержит ссылку на него. Внутри List использует массив для хранения элементов. Когда массив заполняется, List создаёт новый массив большего размера и копирует туда все элементы.
using System.Collections.Generic; // необходимо для List
// Пустой список
List<int> numbers = new List<int>();
// Список с начальной ёмкостью (Capacity)
List<string> names = new List<string>(100); // сразу выделяем место на 100 элементов
// Список с начальными элементами
List<int> primes = new List<int> { 2, 3, 5, 7, 11 };
List<string> fruits = new List<string> { "яблоко", "банан", "вишня" };
// Можно опустить new (C# 9+ target-typed new):
List<double> prices = new() { 99.9, 149.5, 299.0 };
Capacity — сколько элементов может вместить список без выделения новой памяти. Count — сколько элементов реально в списке. Capacity всегда >= Count. При добавлении элемента, когда Count == Capacity, Capacity удваивается.
Добавление элементов: Add, AddRange, Insert
Add() — добавить в конец
Самый частый метод. Добавляет один элемент в конец списка:
List<int> numbers = new List<int>();
numbers.Add(10); // { 10 }
numbers.Add(20); // { 10, 20 }
numbers.Add(30); // { 10, 20, 30 }
Console.WriteLine(numbers.Count); // 3
AddRange() — добавить коллекцию
Добавляет сразу несколько элементов в конец:
List<int> numbers = new List<int> { 1, 2, 3 };
int[] moreNumbers = { 4, 5, 6 };
numbers.AddRange(moreNumbers); // { 1, 2, 3, 4, 5, 6 }
numbers.AddRange(new[] { 7, 8 }); // { 1, 2, 3, 4, 5, 6, 7, 8 }
// Можно добавить и другой List
List<string> list1 = new() { "a", "b" };
List<string> list2 = new() { "c", "d" };
list1.AddRange(list2); // { "a", "b", "c", "d" }
Insert() — вставить в середину
Вставляет элемент на указанную позицию. Все элементы сдвигаются вправо.
List<int> numbers = new List<int> { 10, 20, 40, 50 };
numbers.Insert(2, 30); // вставить 30 на позицию 2
// Результат: { 10, 20, 30, 40, 50 }
// InsertRange — вставить коллекцию
numbers.InsertRange(0, new[] { 1, 2, 3 });
// Результат: { 1, 2, 3, 10, 20, 30, 40, 50 }
LinkedList<T>.
Удаление элементов: Remove, RemoveAt, Clear
Remove() — удалить по значению
Удаляет первое вхождение указанного элемента. Возвращает true, если элемент был найден и удалён:
List<string> fruits = new() { "яблоко", "банан", "вишня", "банан" };
bool removed = fruits.Remove("банан"); // удаляет ПЕРВЫЙ "банан"
Console.WriteLine(removed); // True
Console.WriteLine(string.Join(", ", fruits)); // яблоко, вишня, банан
removed = fruits.Remove("арбуз");
Console.WriteLine(removed); // False — нет такого
RemoveAt() — удалить по индексу
Удаляет элемент на указанной позиции. Остальные элементы сдвигаются влево:
List<int> numbers = new() { 10, 20, 30, 40, 50 };
numbers.RemoveAt(2); // удалить элемент с индексом 2 (30)
// Результат: { 10, 20, 40, 50 }
numbers.RemoveAt(0); // удалить первый
// Результат: { 20, 40, 50 }
// numbers.RemoveAt(10); // ❌ ArgumentOutOfRangeException!
Clear() — очистить всё
Удаляет все элементы из списка. Count становится 0, но Capacity остаётся прежним:
List<int> numbers = new() { 1, 2, 3, 4, 5 };
Console.WriteLine(numbers.Count); // 5
numbers.Clear();
Console.WriteLine(numbers.Count); // 0
Console.WriteLine(numbers.Capacity); // ёмкость сохранилась (например, 8)
Поиск в списке: Contains, IndexOf, Find, FindAll
Contains() — проверка наличия
Возвращает true, если элемент есть в списке:
List<string> fruits = new() { "яблоко", "банан", "вишня" };
bool hasApple = fruits.Contains("яблоко"); // true
bool hasMelon = fruits.Contains("дыня"); // false
if (fruits.Contains("банан"))
Console.WriteLine("Банан есть в списке!");
IndexOf() — поиск индекса
Возвращает индекс первого вхождения элемента. Если не найден — -1:
List<int> numbers = new() { 10, 20, 30, 40, 50, 30 };
int idx = numbers.IndexOf(30);
Console.WriteLine(idx); // 2 (первое вхождение)
idx = numbers.IndexOf(99);
Console.WriteLine(idx); // -1 (не найдено)
// Поиск с указанием начального индекса
idx = numbers.IndexOf(30, 3); // ищем с индекса 3
Console.WriteLine(idx); // 5
Find() — поиск по условию
Находит первый элемент, подходящий под условие. Если ничего не найдено — возвращает default(T) (0 для int, null для string):
List<int> numbers = new() { 5, 12, 8, 21, 3, 17 };
int firstBig = numbers.Find(n => n > 10);
Console.WriteLine(firstBig); // 12
int firstEven = numbers.Find(n => n % 2 == 0);
Console.WriteLine(firstEven); // 12
List<string> names = new() { "Анна", "Борис", "Вика" };
string longName = names.Find(name => name.Length > 4);
Console.WriteLine(longName); // "Борис"
FindAll() — все подходящие элементы
Возвращает новый список со всеми элементами, удовлетворяющими условию:
List<int> numbers = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<int> evenNumbers = numbers.FindAll(n => n % 2 == 0);
Console.WriteLine(string.Join(", ", evenNumbers)); // 2, 4, 6, 8, 10
List<int> bigNumbers = numbers.FindAll(n => n > 5);
Console.WriteLine(string.Join(", ", bigNumbers)); // 6, 7, 8, 9, 10
// FindAll возвращает новый список — изменения не влияют на оригинал
bigNumbers[0] = 999;
Console.WriteLine(numbers[5]); // 6 — оригинал не изменился
Exists() и TrueForAll()
Аналогично массивам:
List<int> numbers = new() { 1, 2, 3, 4, 5 };
bool hasEven = numbers.Exists(n => n % 2 == 0); // true
bool allPositive = numbers.TrueForAll(n => n > 0); // true
bool allEven = numbers.TrueForAll(n => n % 2 == 0); // false (есть нечётные)
Свойство Count и доступ по индексу
List поддерживает доступ по индексу через квадратные скобки, как и массив. Свойство Count — это аналог Length у массивов:
List<int> numbers = new() { 10, 20, 30, 40, 50 };
Console.WriteLine(numbers.Count); // 5
Console.WriteLine(numbers[0]); // 10
Console.WriteLine(numbers[^1]); // 50 (C# 8+)
Console.WriteLine(numbers[numbers.Count - 1]); // 50 (classic)
// Изменить по индексу
numbers[2] = 999;
Console.WriteLine(numbers[2]); // 999
Сортировка: Sort()
В отличие от массивов (где используется Array.Sort()), у List есть собственный метод Sort():
List<int> numbers = new() { 5, 2, 8, 1, 9, 3 };
numbers.Sort();
Console.WriteLine(string.Join(", ", numbers)); // 1, 2, 3, 5, 8, 9
List<string> names = new() { "Вика", "Анна", "Борис" };
names.Sort();
Console.WriteLine(string.Join(", ", names)); // Анна, Борис, Вика
// Сортировка по убыванию:
numbers.Sort();
numbers.Reverse();
Console.WriteLine(string.Join(", ", numbers)); // 9, 8, 5, 3, 2, 1
// Или через Comparison:
numbers.Sort((a, b) => b.CompareTo(a)); // по убыванию
Перебор: for и foreach
Цикл for с индексом:
List<int> numbers = new() { 10, 20, 30, 40, 50 };
for (int i = 0; i < numbers.Count; i++)
{
Console.WriteLine($"numbers[{i}] = {numbers[i]}");
}
Цикл foreach проще и безопаснее:
foreach (int num in numbers)
{
Console.WriteLine(num);
}
Различия массива и списка
Когда использовать массив, а когда список?
Большой пример — управление списком задач
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> tasks = new List<string>();
// Добавляем задачи
tasks.Add("Купить продукты");
tasks.Add("Сделать зарядку");
tasks.Add("Выучить C#");
tasks.Add("Позвонить маме");
Console.WriteLine($"Всего задач: {tasks.Count}");
// Добавляем срочную задачу в начало
tasks.Insert(0, "СРОЧНО: Оплатить счета");
// Выводим все задачи
for (int i = 0; i < tasks.Count; i++)
{
Console.WriteLine($"{i + 1}. {tasks[i]}");
}
// Отмечаем задачу как выполненную
bool done = tasks.Remove("Сделать зарядку");
Console.WriteLine($"Задача 'Сделать зарядку' выполнена: {done}");
// Ищем задачу по ключевому слову
string urgent = tasks.Find(t => t.Contains("СРОЧНО"));
Console.WriteLine($"Срочная задача: {urgent}");
// Добавляем ещё задач через AddRange
tasks.AddRange(new[] {
"Почитать книгу",
"Убраться в комнате",
"Помочь проекту"
});
// Сортируем
tasks.Sort();
Console.WriteLine($"\nСписок после сортировки ({tasks.Count} задач):");
foreach (string task in tasks)
{
Console.WriteLine($" - {task}");
}
// Очистка
// tasks.Clear();
}
}
Полезные методы List<T>
Что важно запомнить
new List<int>() или new List<int> { 1, 2, 3 }.list[0] и list[^1].Contains() — проверка, IndexOf() — индекс, Find() — по условию, FindAll() — все по условию.list.Sort() — по возрастанию. Комбинация Sort() + Reverse() — по убыванию.Тест: 5.2: Список (List<T>)
3 вопроса