Зачем нужна упаковка в си шарп

от admin

Name already in use

CLR-via-C-Sharp-NOTES-RUS / prektirovanie-tipov / primitivi-ssilochnie-i-znachimie-tipi / upakovka-i-raspakovka-znachimih-tipov.md

  • Go to file T
  • Go to line L
  • Copy path
  • Copy permalink
  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

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

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

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

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

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

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 базового метода передавалась ссылка на объект в куче.

Упаковка и распаковка в .NET

Упаковка и распаковка в .NET

7296

Мы уже знаем особенности работы с памятью и доступные структуры данных в .NET приложениях, в этом посте мы разберем упаковку и распаковку, а также рассмотрим, как эти две операции влияют на производительность приложения.

Что такое упаковка и распаковка?

Зачем нам задумываться об упаковке и распаковке? Разве это не обязанность .NET-среды, которая следит за управлением данных и, соответственно, сама "выбирает" наиболее оптимальный способ их хранения?

На самом деле — нет. Что очень важно знать и понимать — так это механизм перемещения данных из области стека в кучу — и наоборот.

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

К примеру, здесь мы имеем следующий пример упаковки:

А вот состояние памяти в момент произведения операции:

Чтобы сохранить значение "123" в виде объекта, в куче создается "упаковка", куда впоследствии и перемещаются данные.

Когда же производится распаковка:

Вот что происходит с памятью:

Значение "123" было изъято из упаковки и помещено назад в область стека.

Заметьте, что когда тип данных i упаковывается внутри объекта o, в стеке хранится лишь ссылка, в то время как само значение хранится в куче. Как только производиться распаковка, данные в куче обязаны быть скопированы в стек (переменная j). В обоих случаях наша цель — это работать с тем самым значением (123).

Как вы можете себе представить, сии операции могут быть достаточно ресурсоемкими.

Тема связана со специальностями:

Давайте рассмотрим IL

Когда мы производим подобный анализ производительности, часто бывает полезно заглянуть непосредственно в Intermediate Language (IL).

Мы еще не рассматривали эту концепцию, но, как вы наверняка знаете, когда мы производим компиляцию в DLL или EXE, выходной файл на самом деле содержит IL — промежуточный код, который в последствии исполняется JIT и впоследствии — виртуальной машиной. Среда выполнения .NET обязана как-то знать, нужно ли упаковывать или распаковывать определенные переменные. Поэтому для обозначения этих операций также требуются дополнительные затраты памяти.

Давайте создадим несложное .NET консольное приложение:

Теперь скомпилируем приложение и при помощи утилитки ILSpy посмотрим его код внутри EXE.

Как только EXE-файл будет открыт в ILSpy, пронавигируемся к методу Main, выбрав "IL with C#".

Заметьте, что операция box выполняется только после присвоение ссылочному типу значения значимого. И наоборот: unbox.any — только после попытки присвоить ссылочному типу данных значимой переменной.

Это де-факто способ, которым операции упаковки и распаковки представлены в IL.

Когда стоит производить упаковку и распаковку?

Код в примере выше скорее всего вам покажется наивным, и вы можете подумать: "Эй, что за вздор! Я никогда не буду такого делать". Что же, в большинстве случаев это действительно так. Но данные в нашем приложении часто упаковываются и распаковываются, когда мы об этом даже не догадываемся.

Гетерогенные коллекции

К примеру, старая школа до сих пор может похвастаться ArrayList.

Метод добавления элемента здесь, как можно отметить, принимает object-параметр.

Таким образом, и здесь производится наша излюбленная упаковка.

Впрочем, подобное кануло в лету с приходом обобщений и обобщенных коллекций.

Конкатенация строк

Другой интересный пример в виде конкатенации строк.

Эта операция требует наличия метода String.Concat, который принимает два object-параметра.

Видео курсы по схожей тематике:

Практикум курса C# Стартовый на примерах из GTA 5

Практикум курса C# Стартовый на примерах из GTA 5

ASP.NET Базовый

Дабы избежать подобных ситуаций, нам достаточно просто немного изменить код, используя на переменной типа int метод ToString (и здесь стоит проигнорировать сообщение ReSharper о том, что операция бессмысленна:) ).

И все! Никакой упаковки больше нет.

Вообще, это далеко не единичные примеры для демонстрации. Но цель нашей статьи — донести четкое представление о том, что такое упаковка и распаковка и когда они применяются.

Производительность

Как мы уже говорили, упаковка и распаковка требуют определенных затрат производительности. В случае с конкатенацией строк, выигрыш от применения ToString весьма незначителен. Именно потому, как я упомянул выше, даже ReSharper не советовал нам делать подобное:

В этом случае гораздо лучше сохранить читабельность кода без ToString.

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

Упакованные значения могут также требовать больше памяти, чем значения в стеке. Копирование значений в/из стека также требует своих затрат. Согласно MSDN, упаковка может занимать порядка 20 раз больше времени, нежели простое присвоение. В то время как распаковка примерно в 4 раза медленней простого присвоения.

Итак. зачем же тогда вообще нужно использовать упаковку и распаковку?

  • .NET-стандарт обладает общей системой типов, что позволяет представлять и ссылочные. и значимые типы схожим образом — и все это благодаря упаковке.
  • Коллекции можно было использовать для хранения значимых типов до появления обобщений.
  • Упрощения кода, вроде конкатенации строк и так далее.

Упаковка и распаковка настолько распространены, что мы не может избежать их полностью. Мы должны знать принцип их работы, чтобы минимизировать их использование, но к этому нужно подходить разумно. Не тратьте свое время на постоянную оптимизацию кода, частую проверку через IL, чтобы убедиться, дабы ни одна лишняя операция упаковки не была использована. Помните, что чистота и простота чтения кода иногда значительно более важна, нежели незаметное, мельчайшее ускорение работы программы.

Бесплатные вебинары по схожей тематике:

Карта специальности .NET разработчик.

Карта специальности .NET разработчик.

Работа с Microsoft Word на C#

Работа с Microsoft Word на C#

Обзор современной разработки под .NET Часть 2

Обзор современной разработки под .NET Часть 2

Подведем итоги

В сегодняшнем уроке мы рассмотрели, что такое упаковка и распаковка, как она представлена в IL-коде, и какое влияние на производительность они имеют. Искренне надеюсь, моя статья сумела прояснить некоторые общие концепции, хотя бы чуть-чуть. 🙂

В грядущих статьях мы рассмотрим механизм сборки мусора. Если у вас есть идеи или пожелания касательно материала новых статьей — милости просим в комментарии!

C#. Упаковка и распаковка. Необходимость (преимущества) применения обобщений

Упаковка и распаковка. Необходимость (преимущества) применения обобщений. Повышение типовой безопасности с помощью обобщений

Содержание

Related Posts