Противоположная операция также разрешена и называется распаковкой (unboxing). Распаковка представляет собой процесс преобразования значения, хранящегося в объектной ссылке, обратно в соответствующий тип значения в стеке. Синтаксически операция распаковки выглядит как обычная операция приведения, но ее семантика несколько отличается. Среда CoreCLR начинает с проверки того, что полученный тип данных эквивалентен упакованному типу, и если это так, то копирует значение в переменную, находящуюся в стеке. Например, следующие операции распаковки работают успешно при условии, что лежащим в основе типом
boxedInt
действительно является
int
:
static void SimpleBoxUnboxOperation
{
//
Создать переменную ValueType (int).
int myInt = 25;
// Упаковать int в ссылку на object.
object boxedInt = myInt;
// Распаковать ссылку обратно в int.
int unboxedInt = (int)boxedInt;
}
Когда компилятор C# встречает синтаксис упаковки/распаковки, он выпускает код CIL, который содержит коды операций
} // end of method '<Program>$'::'<<Main>$>g__SimpleBoxUnboxOperation|0_0'
Помните, что в отличие от обычного приведения распаковка обязана осуществляться только в подходящий тип данных. Попытка распаковать порцию данных в некорректный тип приводит к генерации исключения
InvalidCastException
. Для обеспечения высокой безопасности каждая операция распаковки должна быть помещена внутрь конструкции
try/catch
, но такое действие со всеми операциями распаковки в приложении может оказаться достаточно трудоемкой задачей. Ниже показан измененный код, который выдаст ошибку из-за того, что в нем предпринята попытка распаковки упакованного значения
int
в тип
long
:
static void SimpleBoxUnboxOperation
{
// Создать переменную ValueType (int).
int myInt = 25;
// Упаковать int в ссылку на object.
object boxedInt = myInt;
// Распаковать в неподходящий тип данных, чтобы
// инициировать исключение времени выполнения.
try
{
long unboxedLong = (long)boxedInt;
}
catch (InvalidCastException ex)
{
Console.WriteLine(ex.Message);
}
}
На
первый взгляд упаковка/распаковка может показаться довольно непримечательным средством языка, с которым связан больше академический интерес, нежели практическая ценность. В конце концов, необходимость хранения локального типа значения в локальной переменной
object
будет возникать нечасто. Тем не менее, оказывается, что процесс упаковки/распаковки очень полезен, поскольку позволяет предполагать, что все можно трактовать как
System.Object
, а среда CoreCLR самостоятельно позаботится о деталях, касающихся памяти.
Давайте обратимся к практическому применению описанных приемов. Мы будем исследовать класс
System.Collections.ArrayList
и использовать его для хранения порции числовых (расположенных в стеке) данных. Соответствующие члены класса
ArrayList
перечислены ниже. Обратите внимание, что они прототипированы для работы с данными типа
System.Object
. Теперь рассмотрим методы
Add
,
Insert
и
Remove
, а также индексатор класса:
public class ArrayList : IList, ICloneable
{
...
public virtual int Add(object? value);
public virtual void Insert(int index, object? value);
public virtual void Remove(object? obj);
public virtual object? this[int index] {get; set; }
}
Класс
ArrayList
был построен для оперирования с экземплярами
object
, которые представляют данные, находящиеся в куче, поэтому может показаться странным, что следующий код компилируется и выполняется без ошибок:
static void WorkWithArrayList
{
// Типы значений автоматически упаковываются при передаче
// методу, который требует экземпляр типа object.
ArrayList myInts = new ArrayList;
myInts.Add(10);
myInts.Add(20);
myInts.Add(35);
}
Хотя здесь числовые данные напрямую передаются методам, которые требуют экземпляров типа
object
, исполняющая среда выполняет автоматическую упаковку таких основанных на стеке данных. Когда позже понадобится извлечь элемент из
ArrayList
с применением индексатора типа, находящийся в куче объект должен быть распакован в целочисленное значение, расположенное в стеке, посредством операции приведения. Не забывайте, что индексатор
ArrayList
возвращает элементы типа
System.Object
, а не
System.Int32
:
static void WorkWithArrayList
{
// Типы значений автоматически упаковываются,
// когда передаются члену, принимающему object.
ArrayList myInts = new ArrayList;
myInts.Add(10);
myInts.Add(20);
myInts.Add(35);
// Распаковка происходит, когда объект преобразуется