Однозначная разница между коллекциями, массивами и дженериками, и их внутреннее устройство C#
Запутался в коллекциях, нужны кратко ключевые моменты по всем коллекциям.
Можно ли назвать массив - коллекцией? Здравый смысл говорит да, ибо это коллекция элементов, но в шарпе есть массивы, есть коллекции, и есть дженерики. И не все с ними понятно.
- Мы знаем, что коллекции - это старый вариант универсальных механизмов группировок элементов, которые были ДО дженериков Это ArrayList, Stack, Queue, Hashtable
Все что ты туда добавляешь, сохраняется как object, из-за чего происходит упаковка-распаковка, и если переменная была типа значения, например int, то мы её копируем - и получаем в результате две переменных.
Вопрос - это работает только в коллекциях, или простой массив работает таким же образом?
Происходит ли в массиве int упаковка-распаковка при присвоении значения ячейки? Копируем ли мы значение int при присвоении значения ячейки массива, или это работает как то иначе?
- В Рихтере написано, что одно из преимуществ дженериков является отсутствие упаковки - распаковки, т.к. дженерики строго типизированные и не преобразуются в объект и обратно, так и храняться.
Но они ведь храняться в куче? И всё равно мы имеем копирование значений, если исходный тип - тип значения?
Верно ли, что абсолютно для всех коллекций ссылочные типы работают одинаково - не копируются и просто хранят в своих ячейках ссылки на экземпляры? Основная разница в массивах, коллекциях и дженериках проявляется именно при обращении с типом значения?
Все коллекции и массивы - ссылочные типы, и все их ячейки со всем их содержимым хранятся в куче?
Что под капотом у коллекций? Как внутри реализован например ArrayList или List? Прочитал что у Dictionary под капотом Hashtable, и он просто использует хеш ключа для поиска, а как работает сама Hashtable? Есть ли у неё внутри массив?
Вопросы пересекаются и немного запутаны, ибо я в них вложил все что знаю о коллекциях. Вроде и пользуюсь, но разрозненно все, и вот сейчас Рихтера прочитал и вообще запутался.
Если есть краткая выжимка о сравнении всех коллекций и их внутренней реализации в шарпе, буду благодарен.
Ответы (2 шт):
Generic - обобщение.
Коллекции могут обобщённые и необобщённые.
Массивы изначально имели особый статус и, по сути, были обобщёнными всегда.
Вас, похоже, пугает копирование значений. Но этого не нужно бояться, потому что значимые типы принято делать маленькими и их копирование сравнимо с копированием указателей ссылочных типов.
Раз уж вы читали Рихтера, значит морально готовы к боли и унижению... В современных реализациях дотнета ссылочные типы могут создаваться на стеке! Но это оптимизация рантайма и нас особо волновать не должна.
Разница между обобщёнными и необобщёнными коллекциями проявляется не только при обращении с типом значения. С дженериками не нужен каст (приведение) из object при работе со ссылочными типами. То есть использовать их намного проще и безопаснее.
Приведу схему, как хранятся значимые типы в массивах и дженерик-коллекциях типа List<T>:
[value1|value2|value3]
А вот так они будут храниться в необобщённой коллекции:
[ref1|ref2|ref3]
ref1: box(value1)
ref2: box(value2)
ref3: box(value3)
В самой коллекции - ссылки. А упакованные (boxed) значения раскиданы по куче.
- Все коллекции и массивы - ссылочные типы, и все их ячейки со всем их содержимым хранятся в куче?
Не обязательно. Вот, например, ImmutableArray Struct - как видите, это значимый тип. А что там внутри - неважно.
Никто и ничто не мешает создать структуру-коллекцию, хранящую набор значений в полях. Реализовать индексатор, энумератор - все атрибуты коллекции. При этом куча не используется.
stackalloc - стандартный способ создания массивов на стеке.
- Что под капотом у коллекций?
Если смотреть на этот вопрос широко, в философском смысле, то ответ: что угодно!
Там может быть кусок памяти выделенной на стеке или в куче, а может быть и прямое обращение к файлу, базе данных или сервису по любому протоколу.
Про реализацию Hash table можете почитать в Випипедии (меня в своё время ошарашило её внутреннее устройство, сперва поразила сложность, а в итоге очаровала красота способа).
Там же ознакомьтесь с прочими структурами данных. Если владеете английским, переключитесь на него.
Вот частичный ответ.
Технически в 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/