Язык программирования C#9 и платформа .NET5
static void SimpleBoxUnboxOperation(){// Создать переменную ValueType (int).int myInt = 25;// Упаковать int в ссылку на object.object boxedInt = myInt;}Упаковку можно формально определить как процесс явного присваивания данных типа значения переменной
. При упаковке значения среда CoreCLR размещает в куче новый объект и копирует в него величину типа значения (в данном случае 25). В качестве результата возвращается ссылка на вновь размещенный в куче объект.System.ObjectПротивоположная операция также разрешена и называется распаковкой (unboxing). Распаковка представляет собой процесс преобразования значения, хранящегося в объектной ссылке, обратно в соответствующий тип значения в стеке. Синтаксически операция распаковки выглядит как обычная операция приведения, но ее семантика несколько отличается. Среда CoreCLR начинает с проверки того, что полученный тип данных эквивалентен упакованному типу, и если это так, то копирует значение в переменную, находящуюся в стеке. Например, следующие операции распаковки работают успешно при условии, что лежащим в основе типом
действительно являетсяboxedInt:intstatic void SimpleBoxUnboxOperation(){// Создать переменную ValueType (int).int myInt = 25;// Упаковать int в ссылку на object.object boxedInt = myInt;// Распаковать ссылку обратно в int.int unboxedInt = (int)boxedInt;}Когда компилятор C# встречает синтаксис упаковки/распаковки, он выпускает код CIL, который содержит коды операций
. Если вы просмотрите сборку с помощью утилитыbox/unbox, то обнаружите в ней показанный далее код CIL:ildasm.exe.method assembly hidebysig staticvoid '<<Main>$>g__SimpleBoxUnboxOperation|0_0'() cil managed{.maxstack 1.locals init (int32 V_0, object V_1, int32 V_2)IL_0000: nopIL_0001: ldc.i4.s 25IL_0003: stloc.0IL_0004: ldloc.0IL_0005: box [System.Runtime]System.Int32IL_000a: stloc.1IL_000b: ldloc.1IL_000c: unbox.any [System.Runtime]System.Int32IL_0011: stloc.2IL_0012: ret} // end of method '<Program>$'::'<<Main>$>g__SimpleBoxUnboxOperation|0_0'Помните, что в отличие от обычного приведения распаковка обязана осуществляться только в подходящий тип данных. Попытка распаковать порцию данных в некорректный тип приводит к генерации исключения
. Для обеспечения высокой безопасности каждая операция распаковки должна быть помещена внутрь конструкцииInvalidCastException, но такое действие со всеми операциями распаковки в приложении может оказаться достаточно трудоемкой задачей. Ниже показан измененный код, который выдаст ошибку из-за того, что в нем предпринята попытка распаковки упакованного значенияtry/catchв типint:longstatic void SimpleBoxUnboxOperation(){// Создать переменную ValueType (int).int myInt = 25;// Упаковать int в ссылку на object.object boxedInt = myInt;<b> // Распаковать в неподходящий тип данных, чтобы</b><b> // инициировать исключение времени выполнения.</b>try{long unboxedLong = (long)boxedInt;}catch (InvalidCastException ex){Console.WriteLine(ex.Message);}}На первый взгляд упаковка/распаковка может показаться довольно непримечательным средством языка, с которым связан больше академический интерес, нежели практическая ценность. В конце концов, необходимость хранения локального типа значения в локальной переменной
будет возникать нечасто. Тем не менее, оказывается, что процесс упаковки/распаковки очень полезен, поскольку позволяет предполагать, что все можно трактовать какobject, а среда CoreCLR самостоятельно позаботится о деталях, касающихся памяти.System.ObjectДавайте обратимся к практическому применению описанных приемов. Мы будем исследовать класс
и использовать его для хранения порции числовых (расположенных в стеке) данных. Соответствующие члены классаSystem.Collections.ArrayListперечислены ниже. Обратите внимание, что они прототипированы для работы с данными типаArrayList. Теперь рассмотрим методыSystem.Object,Add()иInsert(), а также индексатор класса:Remove()public class ArrayList : IList, ICloneable{...public virtual int Add(<b>object?</b> value);public virtual void Insert(int index, <b>object?</b> value);