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). При упаковке экземпляра значимого типа происходит следующее:
- В управляемой куче выделяется память для объекта значимого типа. Его размер определяется длиной значимого типа и двумя членами — указатель на объект-тип и индекс блока синхронизации. Последние два необходимы для всех объектов в управляемой куче.
- Поля значимого типа копируются в память, только что выделенную в куче
- Возвращается адрес объекта. Этот адрес является ссылкой на объект, т.е значимый тип превращается в ссылочный
Компилятор C# создает IL-код, необходимый для упаковки экземпляра значимого типа, автоматически
Point p = (Point) a[0];
Здесь ссылка (или указатель), содержащаяся в элементе с номером 0 массива ArrayList, помещается в переменную p значимого типа Point. Для этого все поля, содержащиеся в упакованном объекте Point, надо скопировать в переменную p значимого типа, находящуюся в стеке потока. CLR выполняет эту процедуру в два этапа. Сначала извлекается адрес полей Point из упакованного объекта Point. Этот процесс называют распаковкой (unboxing). Затем значения полей копируются из кучи в экземпляр значимого типа, находящийся в стеке.
Распаковка не является точной противоположностью упаковки. Она гораздо менее ресурсозатратна, чем упаковка, и состоит только в получении указателя на исходный значимый тип (поля данных), содержащийся в объекте.
При распаковке происходит следующее:
- Если переменная, содержащая ссылку на упакованный значимый тип, равна null, генерируется исключение NullReferenceException
- Если ссылка указывает на объект, не являющийся упакованным значением требуемого значимого типа, генерируется исключение Invalid CastException
- В случае, когда всё хоршо, то происходит извлечение из упаковки типа значения и помещение его в стек.
. Хотя неупакованные значимые типы не имеют указателя на типовой объект, вы все равно можете вызывать виртуальные методы (такие, как Equals, GetHashCode или ToString), унаследованные или прееопределенные этим типом. Если ваш значимый тип переопределяет один из этих виртуальных методов, CLR может вызвать метод невиртуально, потому что значимые типы неявно запечатываются и поэтому не могут выступать базовыми классами других типов. Кроме того, экземпляр значимого типа, используемый для вызова виртуального метода, не упаковывается. Но если ваше переопределение виртуального метода вызывает реализацию этого метода из базового типа, экземпляр значимого типа упаковывается при вызове реализации базового типа, чтобы в указателе this базового метода передавалась ссылка на объект в куче.
Упаковка и распаковка в .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


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

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

В этом случае гораздо лучше сохранить читабельность кода без ToString.
Целесообразность оптимизации появляется, как правило, тогда, когда операции упаковки и распаковки предстоит производить в цикле сотни и тысячи раз. В этом случае время выполнения кода с упаковкой может составлять порядка 150 процентов от времени исполнения кода без нее (вы можете сами создать тестовое приложение и сравнить требуемый промежуток времени).
Упакованные значения могут также требовать больше памяти, чем значения в стеке. Копирование значений в/из стека также требует своих затрат. Согласно MSDN, упаковка может занимать порядка 20 раз больше времени, нежели простое присвоение. В то время как распаковка примерно в 4 раза медленней простого присвоения.
Итак. зачем же тогда вообще нужно использовать упаковку и распаковку?
- .NET-стандарт обладает общей системой типов, что позволяет представлять и ссылочные. и значимые типы схожим образом — и все это благодаря упаковке.
- Коллекции можно было использовать для хранения значимых типов до появления обобщений.
- Упрощения кода, вроде конкатенации строк и так далее.
Упаковка и распаковка настолько распространены, что мы не может избежать их полностью. Мы должны знать принцип их работы, чтобы минимизировать их использование, но к этому нужно подходить разумно. Не тратьте свое время на постоянную оптимизацию кода, частую проверку через IL, чтобы убедиться, дабы ни одна лишняя операция упаковки не была использована. Помните, что чистота и простота чтения кода иногда значительно более важна, нежели незаметное, мельчайшее ускорение работы программы.
Бесплатные вебинары по схожей тематике:

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

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

Обзор современной разработки под .NET Часть 2
Подведем итоги
В сегодняшнем уроке мы рассмотрели, что такое упаковка и распаковка, как она представлена в IL-коде, и какое влияние на производительность они имеют. Искренне надеюсь, моя статья сумела прояснить некоторые общие концепции, хотя бы чуть-чуть. 🙂
В грядущих статьях мы рассмотрим механизм сборки мусора. Если у вас есть идеи или пожелания касательно материала новых статьей — милости просим в комментарии!
C#. Упаковка и распаковка. Необходимость (преимущества) применения обобщений
Упаковка и распаковка. Необходимость (преимущества) применения обобщений. Повышение типовой безопасности с помощью обобщений
Содержание
- 1. Понятие упаковки (boxing) и распаковки (unboxing)
- 2. Какая разница между использованием обобщений и приведением к типу object ? Демонстрация преимуществ применения обобщений. Пример
- 2.1. Преимущество 1. Отсутствие явного приведения типа
- 2.2. Преимущество 2. Обеспечение типовой безопасности в обобщениях
- 2.3. Преимущество 3. Повышение производительности
Поиск на других ресурсах:
1. Понятие упаковки (boxing) и распаковки (unboxing)
Как известно, в .NET Framework все базовые типы ( int , double , char и т.д.) представлены соответствующим классом или структурой ( Integer , Double , Char и т.п.) в общей иерархической структуре классов. В вершине этой структуры лежит тип Object , к которому также можно обращаться по имени object . Это означает, что допускается объявлять переменную типа Object и использовать ее для работы с любым типом как показано ниже
Допускается также использовать переменную типа object в правой части оператора присваивания:
Но в этом случае нужно указывать явное приведение типов, как видно из строки
иначе будет ошибка на этапе компиляции.
Если переменная типа object используется в левой части оператора присваивания, то компилятор выполняет так называемую упаковку. Если переменная или значение типа object используется в правой части оператора присваивания, то компилятор выполняет распаковку.
Таким образом можно дать следующие определения. Упаковка — это процесс сохранения значения простого типа ( int , char , double …) в экземпляре объекта (object). Распаковка — это процесс вытягивания упакованного значения ( int , double , char …) из объекта ( object ). Следующий пример демонстрирует различие между этими терминами:
2. Какая разница между использованием обобщений и приведением к типу object ? Демонстрация преимуществ применения обобщений. Пример
Как было сказано в п. 1 в программах на C# можно объявлять ссылки на тип Object , обращаясь к именам object или Object . Благодаря наследованию, переменным типа Object может быть присвоено значение любых унаследованных типов (смотрите п. 1).
Исходя из вышесказанного, можно сделать вывод, что использование типа Object может заменить обобщения. Тогда возникает резонный вопрос: зачем использовать обобщения, если они целиком могут быть заменены типом object ?
Использование обобщений вместо использования типа object дает следующие преимущества:
- отсутствие явного приведения типа в операторе присваивания при использовании обобщений;
- обеспечение типовой безопасности. Ошибка неправильного приведения типов генерируется уже на этапе компиляции а не на этапе выполнения программы;
- повышение производительности. Для типа object операция присваивания выполняется дольше, поскольку происходит упаковка, распаковка.
В следующих пунктах эти преимущества рассматриваются более подробно.
2.1. Преимущество 1. Отсутствие явного приведения типа
Если используется обобщение, то не нужно выполнять явное приведение типов в операции присваивания как показано на рисунке 1.

Рисунок 1. Отличие в явном приведении к типу int между обобщением и типом object
2.2. Преимущество 2. Обеспечение типовой безопасности в обобщениях
При использовании класса object в качестве типа можно допустить ошибку, которая на этапе компиляции не будет обнаружена. Эта ошибка окажется на этапе выполнения, что неприемлемо.
На рисунке 2 реализованы такие же классы как на рисунке 1. Однако, в функции main() , для обоих классов осуществляется попытка установить значение типа double .
В случае с классом ObjectClass ошибки на этапе компиляции не возникает. Эта ошибка вызовет исключительную ситуацию на этапе выполнения.
В случае с классом GenClass<T> ошибка будет определена на этапе компиляции. Это связано с тем, что создается типизированный код с привязкой к типу int . В этом коде ошибки определяются на этапе компиляции. Это является основным преимуществом обобщений, которые повышают типовую безопасность.

Рисунок 2. Особенности выявления ошибки компилятором для обобщенного и необобщенных класса
2.3. Преимущество 3. Повышение производительности
Использование обобщенных классов дает большую производительность (быстродействие) по сравнению с необобщенными. При присвоении значения типа object другим типам и наоборот, компилятор выполняет упаковку и распаковку (смотрите п. 1). Этот процесс требует больше временных затрат чем использование обобщений. В случае с обобщениями формируется типизированный код с привязкой к конкретному типу, который выполняется быстрее.
Рисунок 3 отражает объявление двух классов ObjectClass и GenClass . В функции main() выделены фрагменты кода, в которых проявляется различие в производительности между объектами ( object ) и обобщениями.

Рисунок 3. Отличие в производительности выполнения кода между обобщенным классом и классом типа object . Операция присваивания для обобщенных классов выполняется быстрее
Зачем нужна упаковка в си шарп
Specified cast is not valid. Error: Incorrect unboxing.
Не секрет, что C# сегодня популярный и динамично развывающийся язык, в отличие от своего прямого конкурента — языка Java, который в плане функциональности переживает период застоя. Основное неоспоримое преимущество Java — настоящая кросплатформенность, а не унылая и ограниченная, как у C#..locals init ([0] int32 val, [1] object obj, [2] int32 valUnboxed)
IL_0000: nop
IL_0001: ldc.i4.5
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: box [mscorlib]System.Int32
IL_0009: stloc.1
IL_000a: ldloc.1
IL_000b: unbox.any [mscorlib]System.Int32
IL_0010: stloc.2
IL_0011: retclass Program
static void Main()
Func< int , BigInteger> fact = null ;
fact = x => x > 1 ? x * fact(x — 1) : 1;
>
>class Program
static void Main()
var fact = YPointCombinator.Create< int , BigInteger>(f => (n) => n > 1 ? n * f(n — 1) : 1);
var power = YPointCombinator.Create< int , int , BigInteger>(f => (x, y) => y > 0 ? x * f(x, y — 1) : 1);
>
>
public static class YPointCombinator
public static Func<T1, T2> Create<T1, T2>(Func<Func<T1, T2>, Func<T1, T2>> f)
return f(r => Create( f )( r ));
>
public static Func<T1, T2, T3> Create<T1, T2, T3>(Func<Func<T1, T2, T3>, Func<T1, T2, T3>> f)
return f((r1, r2) => Create(f)(r1, r2));
>
>Поля private и рефлексия, или плевали мы на ваше ООП
class Sample
private string _x = «No change me!» ;
public override string ToString()
return _x;
>
>
class Program
static void Main()
var sample = new Sample();
typeof (Sample).GetField( «_x» , BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(sample, «I change you. » );
Console .Write(sample);
Console .ReadKey();
>
>«Утиная» типизация и цикл foreach
using System;
using System.Collections.Generic;class Sample
public IEnumerator< int > GetEnumerator()
for ( var i = 0; i < 10; ++i)
yield return i;
>
>
class Program
static void Main()
foreach ( var t in new Sample())
Console .WriteLine(t);
Console .ReadKey();
>
>