Коллекции и массивы
Массивы, ArrayList и обобщённые коллекции, зубчатые массивы, словари и устройство List.
8 вопросов
JuniorТеорияОчень частоЧто такое массив в C#, и каковы его основные свойства во время выполнения?
Что такое массив в C#, и каковы его основные свойства во время выполнения?
Массив — это коллекция фиксированного размера с индексацией от нуля и однородными элементами. Даже int[] является ссылочным типом-объектом в куче, хранящим элементы одного типа. Его Length задаётся при создании и не меняется, а каждый доступ по индексу проверяется на границы и бросает IndexOutOfRangeException за концом.
Типичные ошибки
- ✗Считать, что массив может расти после создания, путая его с
List<T> - ✗Думать, что
int[]— значимый тип на стеке, а не объект в куче - ✗Ожидать, что индекс за границей вернёт значение по умолчанию, а не бросит исключение
Уточняющие вопросы
- →Почему даже
int[]размещается в куче, а не на стеке? - →Что на самом деле делает
Array.Resizeпод капотом?
JuniorТеорияЧастоЧем Array в C# отличается от ArrayList, и что использовать сегодня?
Чем Array в C# отличается от ArrayList, и что использовать сегодня?
Array имеет фиксированную длину и строго типизирован одним типом элемента. ArrayList расширяемый, но хранит каждый элемент как object, поэтому значимые типы упаковываются при вставке, а чтение требует приведения — теряя производительность и типобезопасность. Современный код предпочитает List<T>: расширяемый, но обобщённый, без упаковки и приведений.
Типичные ошибки
- ✗Думать, что
ArrayListобобщён и типобезопасен, какList<T> - ✗Не осознавать, что
ArrayListупаковывает значимые элементы, ведь они хранятся какobject - ✗Использовать устаревший
ArrayListв новом коде вместо обобщённогоList<T>
Уточняющие вопросы
- →Какие две издержки добавляет упаковка в
ArrayListпо сравнению сList<T>? - →Почему получение элемента из
ArrayListтребует явного приведения?
JuniorТеорияЧастоЧто такое IEnumerator, и как foreach использует его для обхода коллекции?
Что такое IEnumerator, и как foreach использует его для обхода коллекции?
IEnumerator — это курсор по последовательности: MoveNext() сдвигает и возвращает, есть ли элемент, Current читает элемент под курсором, а Reset() перематывает назад. Коллекция реализует IEnumerable, чей GetEnumerator() отдаёт свежий перечислитель. foreach — синтаксический сахар, вызывающий GetEnumerator(), крутящий MoveNext() и читающий Current на каждом проходе.
Типичные ошибки
- ✗Менять местами роли
IEnumerable(последовательность) иIEnumerator(курсор) - ✗Думать, что
foreachиспользует целочисленный индекс, а неMoveNext/Current - ✗Считать, что перечислитель материализует всю последовательность сразу, а не двигается лениво
Уточняющие вопросы
- →Почему изменение коллекции во время
foreachбросаетInvalidOperationException? - →Как метод-итератор с
yield returnгенерируетIEnumeratorза вас?
MiddleТеорияЧастоЧем Hashtable отличается от Dictionary<TKey,TValue> в C#?
Чем Hashtable отличается от Dictionary<TKey,TValue> в C#?
Hashtable необобщённый: его ключи и значения — object, поэтому значимые типы упаковываются при вставке, а чтение требует приведений, и он слабо типизирован. Dictionary<TKey,TValue> обобщён и типобезопасен, храня ключи и значения их реальными типами без упаковки. Оба хешируют ключ для поиска в среднем за O(1); обобщённый — современный выбор по умолчанию.
Типичные ошибки
- ✗Думать, что
Hashtableобобщён и типобезопасен, какDictionary<TKey,TValue> - ✗Упускать, что
Hashtableупаковывает значимые ключи и значения, хранимые какobject - ✗Считать, что поиск в словаре — O(log n) по дереву, а не в среднем O(1) по хешу
Уточняющие вопросы
- →Что происходит со сложностью поиска, когда много ключей хешируются в одну корзину?
- →Почему
GetHashCodeиEqualsключа должны быть согласованы, чтобы словарь работал?
MiddleТеорияИногдаЧто такое анонимный тип в C#, и где он обычно используется?
Что такое анонимный тип в C#, и где он обычно используется?
Запись new { Name = x, Age = y } заставляет компилятор сгенерировать безымянный неизменяемый тип с read-only свойствами, выведенными из инициализатора. Так как имени для хранения у него нет, он используется только через var или вывод типов, никогда не объявляется явно. Главное применение — формирование промежуточных результатов в LINQ-проекциях.
Типичные ошибки
- ✗Считать свойства анонимного типа изменяемыми, а не read-only
- ✗Пытаться объявить или вернуть анонимный тип по имени вместо
var - ✗Считать анонимные типы
dynamicили на словаре, а не сгенерированными компилятором
Уточняющие вопросы
- →Почему метод не может вернуть анонимный тип без
objectилиdynamic? - →Когда два выражения анонимного типа переиспользуют один сгенерированный класс?
MiddleТеорияИногдаВ чём разница между зубчатым массивом int[][] и прямоугольным int[,]?
В чём разница между зубчатым массивом int[][] и прямоугольным int[,]?
Зубчатый массив int[][] — это массив независимых строк-массивов, поэтому каждая строка — отдельный объект в куче и строки могут различаться по длине. Прямоугольный массив int[,] — единый непрерывный блок с фиксированными размерностями, заданными при создании, где все строки одной длины. Зубчатый обменивает лишнюю косвенность на неровные размеры строк.
Типичные ошибки
- ✗Считать, что прямоугольный
int[,]допускает строки разной длины - ✗Думать, что зубчатый
int[][]— один непрерывный блок, а не отдельные объекты строк - ✗Полагать, что размерности любого из массивов можно изменить после создания
Уточняющие вопросы
- →Почему прямоугольный
int[,]может иметь лучшую локальность кэша, чем зубчатыйint[][]? - →Как инициализировать зубчатый массив со строками разной длины?
SeniorТеорияИногдаКак List<T> растёт внутри, и чем Count отличается от Capacity?
Как List<T> растёт внутри, и чем Count отличается от Capacity?
List<T> оборачивает внутренний массив. Count — сколько элементов он хранит; Capacity — сколько слотов сейчас в этом массиве. Когда при Add Count достигает Capacity, выделяется больший массив — обычно с удвоением — и существующие элементы копируются в него. Эти копии редки и размазаны по многим вставкам, поэтому Add амортизированно O(1).
Типичные ошибки
- ✗Путать
Count(число элементов) сCapacity(число выделенных слотов) - ✗Думать, что
List<T>— связный список, а не структура на массиве - ✗Считать, что каждый
Addперевыделяет, упуская амортизированное O(1) с удвоением
Уточняющие вопросы
- →Когда предустановка
Capacityчерез конструктор улучшает производительность? - →Уменьшает ли удаление элементов
Capacity, и как вернуть эту память?
SeniorТеорияРедкоЧем Array.Clone() отличается от CopyTo, и какую глубину копирования они дают?
Чем Array.Clone() отличается от CopyTo, и какую глубину копирования они дают?
Array.Clone() выделяет и возвращает совершенно новый массив, а CopyTo записывает элементы в уже существующий массив назначения, которым вы владеете. Оба делают поверхностную копию: значимые элементы копируются побитово, а у ссылочных копируются лишь ссылки, поэтому исходник и копия делят одни и те же объекты.
Типичные ошибки
- ✗Считать, что
Clone()илиCopyToглубоко копируют ссылочные элементы - ✗Ожидать, что
Clone()пишет в существующий массив, какCopyTo - ✗Думать, что изменение общего объекта через копию не затрагивает исходник
Уточняющие вопросы
- →Как получить настоящую глубокую копию массива изменяемых объектов?
- →Какое исключение бросает
CopyTo, когда массив назначения слишком мал?