Коллекции и массивы в C#
Массив в C# — это не «список» и не «буфер, который сам растёт». Это ссылочный тип-объект в куче фиксированного размера, где все элементы одного типа, индексация идёт от нуля, а каждый доступ проверяется на границы. Почти каждая «магическая» ошибка новичка с коллекциями — это неверная модель того, где лежат данные (стек или куча), меняется ли размер и упаковываются ли значимые типы в object.
Ключевое разделение, которое проходит через всю тему: обобщённые коллекции (Dictionary<TKey,TValue>, List<T>) хранят один известный тип без упаковки и с проверкой на этапе компиляции, а необобщённые наследники .NET 1.0 (Hashtable, ArrayList) хранят всё как object, упаковывая значимые типы и требуя приведений при чтении. Понимание этой разницы — а вместе с ней устройства массивов, протокола перечисления IEnumerator и роли анонимных типов в LINQ — превращает случайные факты в связную картину. Полная карта — в слоях ниже.
Карта темы
- Массивы — фиксированный размер, объект в куче даже для
int[], проверка границ, почемуArray.Resizeсоздаёт новый массив. - Многомерные массивы — прямоугольный
int[,]как единый непрерывный блок с равными строками,GetLength. - Зубчатые массивы —
int[][]как массив независимых строк-массивов разной длины; косвенность против локальности кэша. - Dictionary — обобщённый хеш-словарь,
GetHashCode/Equals, поиск в среднем за O(1),TryGetValue. - Hashtable — необобщённая карта на
object, упаковка и приведения, почему это наследие. - ArrayList — растущий необобщённый список на
object; упаковка значимых типов и переход наList<T>. - IEnumerator — курсор
MoveNext/Current/Reset,IEnumerable, и какforeachразворачивается в этот протокол. - Анонимные типы —
new { ... }, безымянный неизменяемый тип, доступный лишь черезvar, в основном для LINQ-проекций.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Считать, что массив растёт после создания | Массив путают с List<T>; Length фиксирован при создании и не меняется |
Думать, что int[] — значимый тип на стеке | Даже int[] — ссылочный объект в куче; присваивание копирует ссылку, а не элементы |
Ждать от индекса за границей default или заворота | Доступ проверяется на границы и бросает IndexOutOfRangeException |
Считать Hashtable типобезопасным как Dictionary | Hashtable хранит object, упаковывает значимые ключи/значения и требует приведений |
Тянуть ArrayList в новый код | Устаревший контейнер упаковывает значения; современный выбор — обобщённый List<T> |
Думать, что зубчатый int[][] — один блок | Это массив отдельных строк-объектов; забытая строка null даёт NullReferenceException |
Изменять коллекцию во время foreach | Перечислитель бросает InvalidOperationException |
| Считать свойства анонимного типа изменяемыми | Они read-only; тип доступен лишь через var, вернуть его по имени нельзя |
Значение для собеседований
Коллекции спрашивают почти на любом junior/middle интервью по C#, но проверяют не список методов, а модель памяти. Кандидат, который объясняет, почему int[] живёт в куче, а ArrayList упаковывает int в object, сразу выделяется на фоне «ну, это списки».
Что обычно проверяют:
- Массив = ссылочный объект в куче фиксированного размера с проверкой границ.
- Разница прямоугольного
int[,]и зубчатогоint[][]— раскладка в памяти и локальность кэша. - Необобщённый
Hashtable/ArrayListпротив обобщённогоDictionary/List<T>: упаковка и типобезопасность. - Протокол
IEnumeratorи какforeachразворачивается вMoveNext/Current. - Что такое анонимный тип и где он уместен (LINQ-проекции).
Типичный неверный ответ: «Hashtable и Dictionary — одно и то же, просто разный синтаксис». Это открывает разговор об упаковке значимых типов в object, приведениях при чтении и о том, почему обобщённый Dictionary — современный выбор по умолчанию.