1. Введение
Давайте честно, массивы — отличная штука, но у них есть ограничения. Нужно поменять размер? Придется создавать новый массив и вручную копировать элементы! Нужно быстро искать элемент по ключу? Массив скажет: "Ну, попробуй сделать это сам".
Реальная жизнь полна сценариев, где массивы неудобны. Представьте себе, что вы пишете приложение для учета книг в библиотеке (привет, наша учебная мини-система!). Сначала у вас 5 книг — вроде бы хватает массива. Через неделю их уже 500, а еще через месяц кто-то вернул книгу, а кто-то потерял... Здесь на помощь приходят коллекции!
Коллекции — это такие контейнеры, которые умеют:
- Автоматически изменять свой размер.
- Обеспечивать быстрый поиск, добавление, удаление.
- Давать более удобные способы обработки данных: сортировку, фильтрацию, групповую обработку.
В .NET есть несколько ключевых "семейств" коллекций. Сегодня мы познакомимся с основными: списки (List<T>), словари (Dictionary<TKey, TValue>), множества (HashSet<T>), очереди, стеки, а также с интерфейсами, которые лежат в их основе.
2. Основные виды коллекций
Несмотря на то, что мы еще не углублялись в конкретные типы обобщенных коллекций, важно понимать, какие существуют основные категории коллекций и для каких задач они подходят. Думайте об этом как о разных видах "контейнеров", каждый со своими особенностями:
Списки (Lists)
Что это? Упорядоченная последовательность элементов. Как обычный список покупок или список студентов в журнале.
Ключевые особенности: Элементы имеют порядок (можно получить по индексу, как в массиве). Могут содержать повторяющиеся элементы. Размер динамический.
Когда использовать? Когда важен порядок следования элементов, и вы хотите иметь возможность быстро добавлять/удалять элементы в конец списка, или получать элементы по их позиции.
Пример из жизни: Очередь на кассе, список участников вебинара, последовательность кадров в анимации.
Словари/Отображения (Dictionaries/Maps)
Что это? Набор пар "ключ-значение". Похоже на настоящий словарь, где у каждого слова (ключа) есть свое значение (определение).
Ключевые особенности: Каждый ключ уникален. Ключ используется для быстрого поиска соответствующего значения. Порядок элементов обычно не гарантируется.
Когда использовать? Когда вам нужно быстро найти значение по какому-то уникальному идентификатору (ключу).
Пример из жизни: Записная книжка (имя - телефон), база данных товаров (ID товара - описание), настройки приложения (имя настройки - значение).
Множества (Sets)
Что это? Неупорядоченный набор уникальных элементов. Как математическое множество.
Ключевые особенности: Не могут содержать повторяющиеся элементы. Порядок элементов не гарантируется. Оптимизированы для проверки наличия элемента и выполнения операций над множествами (объединение, пересечение).
Когда использовать? Когда вам нужно хранить уникальные значения и быстро проверять, присутствует ли элемент в наборе.
Пример из жизни: Список уникальных посетителей сайта, набор тегов для статьи, список слов для автодополнения (без повторов).
Очереди (Queues)
Что это? Коллекция, работающая по принципу "первый пришел — первый вышел" (FIFO - First-In, First-Out).
Ключевые особенности: Элементы добавляются в конец очереди и удаляются из начала.
Когда использовать? Моделирование процессов, где порядок обработки важен, например, система тикетов поддержки, очередь на печать.
Стеки (Stacks)
Что это? Коллекция, работающая по принципу "последний пришел — первый вышел" (LIFO - Last-In, First-Out).
Ключевые особенности: Элементы добавляются и удаляются с одного конца (вершины стека).
Когда использовать? Отслеживание истории действий (отмена действия в редакторе), обработка вложенных структур, рекурсия.
Различия между типами коллекций
| Тип коллекции | Аналогия | Основной принцип | Доступ по индексу? | Порядок элементов? | Дубликаты разрешены? | Основные операции |
|---|---|---|---|---|---|---|
| Массив | Ряд пронумерованных ячеек | Фиксированный размер | Да | Да | Да | Получение/установка по индексу |
| Список | Список покупок | Динамический, упорядоченный | Да | Да | Да | Добавление, удаление, поиск |
| Словарь | Словарь (ключ-значение) | Уникальные ключи | Нет | Нет (обычно) | Нет (по ключу) | Получение по ключу, добавление |
| Множество | Набор уникальных объектов | Только уникальные элементы | Нет | Нет | Нет | Проверка наличия, объединение |
| Очередь | Очередь на кассе | FIFO (Первый пришел - первый ушел) | Нет | Да | Да | Добавление в конец, извлечение из начала |
| Стек | Стопка тарелок | LIFO (Последний пришел - первый ушел) | Нет | Да | Да | Добавление наверх, извлечение сверху |
3. Список: List<T>
Список - это самая часто используемая коллекция в C#. Даже чаще чем массив. Список — коллекция, которая похожа на массив, но умеет расти сама. Можно добавлять и удалять элементы без головной боли.
using System;
using System.Collections.Generic;
var numbers = new List<int>(); // Создаём пустой список целых чисел
numbers.Add(10); // Добавляем элемент
numbers.Add(15);
numbers.Add(42);
Console.WriteLine(numbers[0]); // 10
numbers.Remove(15); // Удаляем элемент по значению
foreach (var number in numbers)
{
Console.WriteLine(number);
}
// Выведет: 10 и 42
Почему List<T> лучше массива для коллекции данных, размер которой заранее неизвестен?
— Потому что не нужно вручную выделять больше памяти и копировать массив при добавлении элементов — всё делает коллекция!
Когда использовать List<T>?
- Динамический список, когда элементы можно добавлять, удалять, изменять.
- Не нужен быстрый поиск по ключу (это про словари).
- Часто используем для работы с упорядоченными наборами.
4. Словарь Dictionary<TKey, TValue>
Массив (и список) позволяет хранить список значений в ячейках массива, и у каждой ячейки есть индекс. А вот словарь - это коллекция, которая позволяем вместо числа (индекса) использовать строку (имя). Такое имя ячейки называется ключом.
Если нужно по какому-то ключу быстро получить значение (например, по номеру читательского билета — имя читателя), то нужен словарь.
using System.Collections.Generic;
var phoneBook = new Dictionary<string, string>();
phoneBook["Аня"] = "+79992221133";
phoneBook["Максим"] = "+79998887766";
Console.WriteLine(phoneBook["Аня"]); // +79992221133
// Можно проверить, есть ли ключ:
if (phoneBook.ContainsKey("Вася"))
{
Console.WriteLine(phoneBook["Вася"]);
}
else
{
Console.WriteLine("Нет такого номера!");
}
Интересный факт: Словари часто называют "ассоциативными массивами". Они реализованы на основе хеш-таблицы, что позволяет выполнять поиск по ключу очень быстро (почти мгновенно, если не считать коллизии — но об этом чуть позже).
Ключевые особенности
- Ключ уникален: в словаре не может быть двух одинаковых ключей.
- Значения могут повторяться.
- Очень быстро ищет, добавляет, удаляет элементы по ключу.
5. Множество: HashSet<T>
Кроме списков и словарей еще очень популярны множества. Это почти как список, только проще: список без фиксированного порядка элементов. Такое множество просто хранит набор значений, для ситуаций, когда порядок не важен.
Например, вам нужно просто знать, есть ли что-то в наборе, без дубликатов, и неважен порядок — тогда используем множество.
using System.Collections.Generic;
var knownUsers = new HashSet<string>();
knownUsers.Add("admin");
knownUsers.Add("guest");
knownUsers.Add("admin"); // Повторное добавление — игнорируется
Console.WriteLine(knownUsers.Contains("admin")); // True
Console.WriteLine(knownUsers.Count); // 2
Множество очень быстро проверяет наличие элемента.
Зачем нужны множества?
— Если, например, хотите хранить всех уникальных пользователей, открывших приложение за месяц, или, скажем, список уникальных авторов книг.
Особенности
- Хранит только уникальные элементы (вставка дубликата игнорируется).
- Нет индексов, как у списка.
- Быстрая проверка наличия.
6. Очереди и стеки
Есть еще структуры для специальных случаев: очередь и стек. Фактически это тот же список, только с регулируемым добавлением и извлечением элементов.
Queue<T>: очередь (first in — first out)
Иногда нужно держать "очередь" — новый элемент встаёт в конец, а забирать их нужно с начала.
using System.Collections.Generic;
var queue = new Queue<string>();
queue.Enqueue("Первый");
queue.Enqueue("Второй");
queue.Enqueue("Третий");
Console.WriteLine(queue.Dequeue()); // "Первый"
Console.WriteLine(queue.Peek()); // "Второй", но не удаляет
Stack<T>: стек (first in — last out)
Стек работает наоборот: последний зашёл — первый вышел. Применяется в парсерах, обработке вызовов функций и даже при отмене действий в редакторах.
using System.Collections.Generic;
var stack = new Stack<string>();
stack.Push("Один");
stack.Push("Два");
stack.Push("Три");
Console.WriteLine(stack.Pop()); // "Три"
Console.WriteLine(stack.Peek()); // "Два"
7. Таблица: сравнение основных коллекций
| Коллекция | Уникальные элементы | Доступ по индексу | Быстрый поиск по ключу | Операции вставки/удаления |
|---|---|---|---|---|
|
Нет | Да | Нет | Быстрый в конец |
|
Ключи | Нет | Да | Быстрый по ключу |
|
Да | Нет | Да* | Быстрый по значению |
|
Нет | Нет | Нет | Быстрый (FIFO) |
|
Нет | Нет | Нет | Быстрый (LIFO) |
Каждую коллекцию мы подробно разберем в следующих лекциях. А начнем мы с загадочной буквы <T>...
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ