Ответ
List
Основные особенности:
- Динамический размер: В отличие от массива (
T[]),List<T>автоматически увеличивает свою внутреннюю емкость при добавлении элементов. - Доступ по индексу: Поддерживает быстрый доступ к элементам по индексу за O(1):
myList[5]. - Богатый API: Содержит множество удобных методов для добавления (
Add,AddRange), удаления (Remove,RemoveAt), поиска (Find,Contains) и манипуляций (Sort,Reverse).
Пример использования:
// Создание списка с начальной емкостью (оптимизация)
List<string> cities = new List<string>(capacity: 10);
// Добавление элементов
cities.Add("Москва");
cities.AddRange(new[] { "Санкт-Петербург", "Новосибирск" });
// Вставка в середину (дорогая операция O(n))
cities.Insert(1, "Екатеринбург");
// Удаление
cities.Remove("Новосибирск"); // Удаляет первое вхождение
cities.RemoveAt(0); // Удаляет элемент по индексу
// Поиск
bool hasMoscow = cities.Contains("Москва");
string firstCity = cities.Find(c => c.StartsWith("С")); // Санкт-Петербург
// Итерация
foreach (var city in cities) { /* ... */ }
Сложность операций и рекомендации:
- Доступ по индексу: O(1).
- Добавление в конец (
Add): В среднем O(1). Если внутренний массив заполнен, происходит выделение нового массива (обычно в 2 раза больше) и копирование элементов (O(n)). - Вставка/удаление в середину: O(n), так как требует сдвига всех последующих элементов.
- Когда использовать: Для хранения коллекций, где часты операции доступа по индексу и добавления в конец. Если нужны частые вставки/удаления в начале или середине, рассмотрите
LinkedList<T>.
Ответ 18+ 🔞
А, List
Короче, что это за зверь:
Это такая умная обёртка над массивом, которая делает вид, что она бесконечная. Внутри всё тот же простой массив, но когда он заполняется, List
Чем хорош:
- Достать что-то по номеру — мгновенно.
myList[5]— и ты уже получил свой элемент, быстрее, чем сосед успел крикнуть "заткнись, там футбол!". - Добавить в конец — обычно тоже быстро. Пока есть место в его внутреннем массиве — всё ок. А когда место кончится... ну, будет небольшая заминка на переезд.
- Методов дохуя. Хочешь добавить, удалить, найти, перевернуть, отсортировать — всё есть. Не нужно, как в старые времена, руками писать циклы на коленке.
Пример, чтобы стало совсем ясно:
// Создаём список. Можно сразу сказать, сколько примерно элементов будет — для приличия.
List<string> пивные = new List<string>(10);
// Наливаем.
пивные.Add("Жигулёвское");
пивные.AddRange(new[] { "Балтика 9", "Очаково" });
// Впихнуть что-то в середину — можно, но дорого. Ему придётся сдвигать всё, что после.
пивные.Insert(1, "Сибирская корона");
// Выпили что-то.
пивные.Remove("Очаково"); // Выкинул первое попавшееся
пивные.RemoveAt(0); // Выкинул самое первое, по индексу
// Поиск.
bool естьЛиБалтика = пивные.Contains("Балтика 9"); // true, надеюсь
string крепкоеПиво = пивные.Find(p => p.Contains("9")); // Найдёт "Балтика 9"
// Перебор, как по магазину.
foreach (var бутылка in пивные)
{
Console.WriteLine($"Открывай {бутылка}!");
}
Где собака зарыта (производительность):
- Взять элемент по индексу (
[5]) — O(1). Быстро, как удар током. - Добавить в конец (
Add) — В среднем O(1). Но если он упёрся в потолок внутреннего массива, то будет операция "расширяемся" — O(n), и все на это время зависнут. - Вставить или удалить где-то в середине (
Insert,Remove) — O(n). Это пиздец как медленно, потому что ему надо будет подвинуть кучу элементов, как шкаф с посудой. Если делаешь это часто — ты конченый мазохист.
Так когда его использовать-то? Используй его, когда тебе чаще всего нужно просто добавлять в конец и доставать по номеру. Это как твоя домашняя коллекция видеокассет — ты их ставишь на полку (в конец) и иногда берёшь посмотреть (по индексу).
А если тебе нужно постоянно вставлять и удалять что-то в начале или середине — это пиздец, братан, выбери что-то другое, например, LinkedList<T>. С ним вставка в середину быстрая, но зато достать что-то по номеру — это уже отдельный квест с похмелья.
Короче, List