Однозначная разница между коллекциями, массивами и дженериками, и их внутреннее устройство C#

Запутался в коллекциях, нужны кратко ключевые моменты по всем коллекциям.

Можно ли назвать массив - коллекцией? Здравый смысл говорит да, ибо это коллекция элементов, но в шарпе есть массивы, есть коллекции, и есть дженерики. И не все с ними понятно.

  1. Мы знаем, что коллекции - это старый вариант универсальных механизмов группировок элементов, которые были ДО дженериков Это ArrayList, Stack, Queue, Hashtable

Все что ты туда добавляешь, сохраняется как object, из-за чего происходит упаковка-распаковка, и если переменная была типа значения, например int, то мы её копируем - и получаем в результате две переменных.

Вопрос - это работает только в коллекциях, или простой массив работает таким же образом?

Происходит ли в массиве int упаковка-распаковка при присвоении значения ячейки? Копируем ли мы значение int при присвоении значения ячейки массива, или это работает как то иначе?

  1. В Рихтере написано, что одно из преимуществ дженериков является отсутствие упаковки - распаковки, т.к. дженерики строго типизированные и не преобразуются в объект и обратно, так и храняться.

Но они ведь храняться в куче? И всё равно мы имеем копирование значений, если исходный тип - тип значения?

  1. Верно ли, что абсолютно для всех коллекций ссылочные типы работают одинаково - не копируются и просто хранят в своих ячейках ссылки на экземпляры? Основная разница в массивах, коллекциях и дженериках проявляется именно при обращении с типом значения?

  2. Все коллекции и массивы - ссылочные типы, и все их ячейки со всем их содержимым хранятся в куче?

  3. Что под капотом у коллекций? Как внутри реализован например ArrayList или List? Прочитал что у Dictionary под капотом Hashtable, и он просто использует хеш ключа для поиска, а как работает сама Hashtable? Есть ли у неё внутри массив?

Вопросы пересекаются и немного запутаны, ибо я в них вложил все что знаю о коллекциях. Вроде и пользуюсь, но разрозненно все, и вот сейчас Рихтера прочитал и вообще запутался.

Если есть краткая выжимка о сравнении всех коллекций и их внутренней реализации в шарпе, буду благодарен.


Ответы (2 шт):

Автор решения: Alexander Petrov

Generic - обобщение.
Коллекции могут обобщённые и необобщённые.

Массивы изначально имели особый статус и, по сути, были обобщёнными всегда.


Вас, похоже, пугает копирование значений. Но этого не нужно бояться, потому что значимые типы принято делать маленькими и их копирование сравнимо с копированием указателей ссылочных типов.

Раз уж вы читали Рихтера, значит морально готовы к боли и унижению... В современных реализациях дотнета ссылочные типы могут создаваться на стеке! Но это оптимизация рантайма и нас особо волновать не должна.


Разница между обобщёнными и необобщёнными коллекциями проявляется не только при обращении с типом значения. С дженериками не нужен каст (приведение) из object при работе со ссылочными типами. То есть использовать их намного проще и безопаснее.


Приведу схему, как хранятся значимые типы в массивах и дженерик-коллекциях типа List<T>:

[value1|value2|value3]

А вот так они будут храниться в необобщённой коллекции:

[ref1|ref2|ref3]

ref1: box(value1)

ref2: box(value2)

ref3: box(value3)

В самой коллекции - ссылки. А упакованные (boxed) значения раскиданы по куче.


  1. Все коллекции и массивы - ссылочные типы, и все их ячейки со всем их содержимым хранятся в куче?

Не обязательно. Вот, например, ImmutableArray Struct - как видите, это значимый тип. А что там внутри - неважно.

Никто и ничто не мешает создать структуру-коллекцию, хранящую набор значений в полях. Реализовать индексатор, энумератор - все атрибуты коллекции. При этом куча не используется.

stackalloc - стандартный способ создания массивов на стеке.


  1. Что под капотом у коллекций?

Если смотреть на этот вопрос широко, в философском смысле, то ответ: что угодно!
Там может быть кусок памяти выделенной на стеке или в куче, а может быть и прямое обращение к файлу, базе данных или сервису по любому протоколу.


Про реализацию Hash table можете почитать в Випипедии (меня в своё время ошарашило её внутреннее устройство, сперва поразила сложность, а в итоге очаровала красота способа).

Там же ознакомьтесь с прочими структурами данных. Если владеете английским, переключитесь на него.

→ Ссылка
Автор решения: pasx

Вот частичный ответ.

Технически в C# массивы не являются коллекциями, потому что они не являются частью System.Collections. Здравый смысл, как вы написали, заставляет нас думать о массивах и коллекциях в целом как о структурах данных, содержащих наборы значений. Подобно тому, что в математике мы называем «множествами». Главное, у массивов нет проблем с упаковкой / распаковкой.

Мы знаем, что коллекции составляют основу ряда структур, используемых для хранения данных в C #. Эти коллекции являются специализированными или расширенными для обеспечения некоторых дополнительных функций - например, стека, очереди, словаря, хеш-таблицы.

В исходных версиях без дженериков используется object. А для типов значений подразумевают использование механизма упаковки / распаковки. Проверить эту статью: "Non-generic collections shouldn't be used" https://github.com/dotnet/platform-compat/blob/master/docs/DE0006.md

Дженерики - это не только особенность коллекции. Они важной особенностью C#. Для коллекций они дают возможность хранить значение, не упаковывая его как object.

Больше информации - вероятно, слишком много - здесь: https://mihai-albert.com/2020/02/10/boxing-performance-in-c-versus-generics/

→ Ссылка