Skip to content

Latest commit

 

History

History
37 lines (22 loc) · 5.98 KB

upakovka-i-raspakovka-znachimih-tipov.md

File metadata and controls

37 lines (22 loc) · 5.98 KB

Упаковка и распаковка значимых типов

Значимые типы «легче» ссылочных: для них не нужно выделять память в управляемой куче, их не затрагивает сборка мусора, к ним нельзя обратиться через указатель. Однако часто требуется получать ссылку на экземпляр значимого типа, например если вы хотите сохранить структуры Point в объекте типа ArrayList (определен в пространстве имен System.Collections) через метод Add()

 p.x = p.y = i; // Инициализация членов в нашем значимом типе
 a.Add(p);      // Упаковка значимого типа и добавление

Упаковка (Boxing)

Для преобразования значимого типа в ссылочный служит упаковка (boxing). При упаковке экземпляра значимого типа происходит следующее:

  1. В управляемой куче выделяется память для объекта значимого типа. Его размер определяется длиной значимого типа и двумя членами - указатель на объект-тип и индекс блока синхронизации. Последние два необходимы для всех объектов в управляемой куче.
  2. Поля значимого типа копируются в память, только что выделенную в куче
  3. Возвращается адрес объекта. Этот адрес является ссылкой на объект, т.е значимый тип превращается в ссылочный

Компилятор C# создает IL-код, необходимый для упаковки экземпляра значимого типа, автоматически

Распаковка(Unboxing)

Point p = (Point) a[0];

Здесь ссылка (или указатель), содержащаяся в элементе с номером 0 массива ArrayList, помещается в переменную p значимого типа Point. Для этого все поля, содержащиеся в упакованном объекте Point, надо скопировать в переменную p значимого типа, находящуюся в стеке потока. CLR выполняет эту процедуру в два этапа. Сначала извлекается адрес полей Point из упакованного объекта Point. Этот процесс называют распаковкой (unboxing). Затем значения полей копируются из кучи в экземпляр значимого типа, находящийся в стеке.

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

При распаковке происходит следующее:

  1. Если переменная, содержащая ссылку на упакованный значимый тип, равна null, генерируется исключение NullReferenceException
  2. Если ссылка указывает на объект, не являющийся упакованным значением требуемого значимого типа, генерируется исключение Invalid CastException
  3. В случае, когда всё хоршо, то происходит извлечение из упаковки типа значения и помещение его в стек.

!!! Хотя неупакованные значимые типы не имеют указателя на типовой объект, вы все равно можете вызывать виртуальные методы (такие, как Equals, GetHashCode или ToString), унаследованные или прееопределенные этим типом. Если ваш значимый тип переопределяет один из этих виртуальных методов, CLR может вызвать метод невиртуально, потому что значимые типы неявно запечатываются и поэтому не могут выступать базовыми классами других типов. Кроме того, экземпляр значимого типа, используемый для вызова виртуального метода, не упаковывается. Но если ваше переопределение виртуального метода вызывает реализацию этого метода из базового типа, экземпляр значимого типа упаковывается при вызове реализации базового типа, чтобы в указателе this базового метода передавалась ссылка на объект в куче.

Вместе с тем вызов невиртуального унаследованного метода (такого, как GetType или MemberwiseClone) всегда требует упаковки значимого типа, так как эти методы определены в System.Object, поэтому методы ожидают, что в аргументе this передается указатель на объект в куче.