
15.4. Упаковка и распаковка
Когда значение структурного типа преобразуется к типу object или приводится к типу того интерфейса, который реализован структурой, выполняется операция упаковки (boxing). Эта операция выполняется автоматически и не требует вмешательства программиста.
Упаковкой называют процесс явного преобразования из типа значений в тип ссылок. При упаковке создаётся и размещается куче объект, которому присваивается значение объекта с типом значения. Возвращаемым значением при выполнении упаковки служит ссылка на объект кучи.
Обратной операцией является распаковка (unboxing), при которой значение объекта присваивается переменной с типом значения.
Автоматическая упаковка выполняется в тех случаях, когда выполняется допустимое присваивание ссылке на объект ссылочного типа переменной с типом значения. Так как все классы языка С# имеют общий базовый класс object , то ссылке типа object можно присвоить значение структуры. В этом случае выполняется автоматическая упаковка, не требующая вмешательства программиста. Обратная процедура - распаковка - автоматически не выполняется. Для распаковки необходимо применять операцию приведения типов.
Сказанное относится не только к присваиванию, но и к передаче параметров и к возвращаемому методом результату. Рассмотрим статический метод с таким заголовком:
static object redouble(object obj)
Метод принимает ссылку на объект типа object и возвращает значение того же типа. Внешне ничто не препятствует применению в обращении к этому методу в качестве аргумента ссылки на объект любого типа. Однако, в теле метода необходимо учитывать конкретный тип аргумента, и формировать возвращаемый результат в соответствии с этим типом. В следующей программе определена структура Struct1 с полем x типа double, и метод с приведенным выше заголовком.
// 15_10.cs - структуры, упаковка, распаковка
struct Struct1 // структура
{
double x;
public double X { get { return x; } set { x = value; } }
}
static object reDouble(object obj)
{
if (obj is Struct1)
{
Struct1 st = (Struct1)obj;
st.X = 2 * st.X;
return st;
}
else
Console.WriteLine("Неизвестный тип!");
return obj;
}
public static void Main()
{
Struct1 one = new Struct1();
one.X = 4;
Struct1 two = (Struct1)reDouble(one);
Console.WriteLine("one.X={0}; two.X={1}", one.X, two.X);
Console.WriteLine("(int)reDouble(55)={0}", (int)reDouble(55));
}
Результат выполнения программы: one.X=4; two.X=8 Неизвестный тип! (int)reDouble(55) = 55
Метод reDouble() обрабатывает только аргументы типа Struct1, хотя ему можно передать аргумент любого типа. Если аргумент имеет тип Struct1, метод reDouble() выполняет его распаковку и удваивает значение поля double х. Если тип аргумента отличен от Struct1, то тип распознаётся как неизвестный и аргумент возвращается в точку вызова в «упакованном» виде. Для примера в методе Main() обращение к reDouble() выполнено дважды с аргументами разных типов.
Понимание процедур упаковки и распаковки необходимо для применения таких библиотечных средств как коллекции. К ним относится класс ArrayList (массив-список) из пространства имён System.Collection. Объект класса ArrayList во многих случаях «ведёт себя» как массив. Например, к нему применима индексация. В отличие от массивов, производных от класса Array, объекты класса ArrayList могут расти в процессе выполнения программы. Количество их элементов увеличивается, как только в этом возникает необходимость, причём рост выполняется автоматически без вмешательства программиста.
Объявление такого растущего массива-списка:
using System.Collections;
ArrayList dinamo = new ArrayList(3);
В данном примере объявлена ссылка dinamo и ассоциированный с нею объект класса ArrayList. В обращении к конструктору ArrayList() указан начальный размер массива-списка. Можно предположить, что теперь можно обращаться к элементам массива-списка, используя индексы со значениями 0, 1, 2. Однако следующая попытка будет ошибочной:
dinamo [1]=45.3; // ошибка времени исполнения
Даже, если в вызове конструктора указан размер массива-списка, первоначально элементов в создаваемом массиве-списке НЕТ! Отличие объекта класса ArrayList от традиционного массива состоит в том, что в массив-список элементы должны быть вначале занесены с помощью нестатического метода Add() класса ArrayList.
Заголовок метода:
public virtual int Add(object value)
Метод добавляет в конец массива-списка элемент, заданный аргументом. Возвращаемое значение - порядковый номер (индекс) добавленного элемента. Нумерация элементов начинается с нуля.
Так как тип параметра object, то в качестве аргумента можно использовать значение любого типа. Следовательно, в один массив-список можно помещать элементы разных типов. Процедура упаковки, необходимая для аргументов с типами значений, выполняется автоматически. А вот при получении значения элемента массива-списка нужно явно выполнить распаковку, узнав предварительно какой тип имеет элемент.
В следующей программе создан массив-список, представляемый ссылкой ArrayList dynamo. Затем в этот массив-список добавлены элементы со значениями double, int и Points, где Points - пользовательский тип, объявленный в программе как структура. Текст программы:
// 15_11.cs - структуры и массив-список типа ArrayList
struct PointS // структура
{
double x, y;
public double X { get { return x; } set { x = value; } }
public double Y { get { return y; } set { y = value; } }
}
public static void Main11()
{
ArrayList dinamo = new ArrayList();
dinamo.Add(4.8);
dinamo.Add(new PointS());
dinamo.Add(100);
PointS ps = new PointS();
ps.X = 10.2;
dinamo.Add(ps);
dinamo[1] = 1.23;
foreach (object ob in dinamo)
if (ob is PointS)
Console.WriteLine("Struct: X={0}; Y={1}", ((PointS)ob).X, ((PointS)ob).Y);
else
if (ob is Double)
Console.WriteLine("Double: Value={0}", ((double)ob).ToString());
}
Результат выполнения программы: Double: Value=4,8 Double: Value=1,23 Struct: X=10,2; Y=0
В методе Main() после размещения в массиве-списке dinamo четырёх элементов, эти элементы перебираются в цикле foreach. Параметр цикла ob имеет тип object. Ему последовательно присваиваются ссылки на разнотипные элементы массива-списка. Непосредственно использовать параметр типа object для доступа к объектам разных типов невозможно - у каждого типа своя структура, свои члены, свои поля. В теле цикла условные операторы, предназначенные для распознавания типов. Распознаются только типы double и Points. Обратите внимание как с помощью выражения с индексацией выполнено присваивание второму элементу массива-списка:
dinamo[1] = 1.23;
Перед этим присваиванием значением элемента dinamo[1] был объект структуры Points.