Чтение онлайн

ЖАНРЫ

Язык программирования C#9 и платформа .NET5
Шрифт:

Противоположная операция также разрешена и называется распаковкой (unboxing). Распаковка представляет собой процесс преобразования значения, хранящегося в объектной ссылке, обратно в соответствующий тип значения в стеке. Синтаксически операция распаковки выглядит как обычная операция приведения, но ее семантика несколько отличается. Среда CoreCLR начинает с проверки того, что полученный тип данных эквивалентен упакованному типу, и если это так, то копирует значение в переменную, находящуюся в стеке. Например, следующие операции распаковки работают успешно при условии, что лежащим в основе типом

boxedInt
действительно является
int
:

static void SimpleBoxUnboxOperation

{

//
Создать переменную ValueType (int).

int myInt = 25;

// Упаковать int в ссылку на object.

object boxedInt = myInt;

// Распаковать ссылку обратно в int.

int unboxedInt = (int)boxedInt;

}

Когда компилятор C# встречает синтаксис упаковки/распаковки, он выпускает код CIL, который содержит коды операций

box/unbox
. Если вы просмотрите сборку с помощью утилиты
ildasm.exe
, то обнаружите в ней показанный далее код CIL:

.method assembly hidebysig static

void '<<Main>$>g__SimpleBoxUnboxOperation|0_0' cil managed

{

.maxstack 1

.locals init (int32 V_0, object V_1, int32 V_2)

IL_0000: nop

IL_0001: ldc.i4.s 25

IL_0003: stloc.0

IL_0004: ldloc.0

IL_0005: box [System.Runtime]System.Int32

IL_000a: stloc.1

IL_000b: ldloc.1

IL_000c: unbox.any [System.Runtime]System.Int32

IL_0011: stloc.2

IL_0012: ret

} // 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);

// Распаковка происходит, когда объект преобразуется

Поделиться с друзьями: