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 '$'::'<

$>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);

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

  // обратно в данные, расположенные в стеке.

  int i = (int)myInts[0];

Перейти на страницу:

Похожие книги