158
Глава.5 .Примитивные,.ссылочные.и.значимые.типы
ПриМеЧание
В.состав.FCL.входит.новое.множество.обобщенных.классов.коллекций,.из-за.которых.
необобщенные.классы.коллекций.считаются.устаревшими .Так,.вместо.класса.System
Collections ArrayList.следует.использовать.класс.System Collections Generic List .
Обобщенные.классы.коллекций.во.многих.отношениях.совершеннее.своих.необоб-
щенных.аналогов .В.частности,.API-интерфейс.стал.яснее.и.совершеннее,.кроме.того,.
повышена.производительность.классов.коллекций .Но.одно.из.самых.ценных.улучше-
ний.заключается.в.предоставляемой.обобщенными.классами.коллекций.возможности.
работать.с.коллекциями.значимых.типов,.не.прибегая.к.их.упаковке/распаковке .Одна.
эта.особенность.позволяет.значительно.повысить.производительность,.так.как.ра-
дикально.сокращается.число.создаваемых.в.управляемой.куче.объектов,.что,.в.свою.
очередь,.сокращает.число.проходов.сборщика.мусора.в.приложении .В.результате.
обеспечивается.безопасность.типов.на.этапе.компиляции,.а.код.становится.понятнее.
за.счет.сокращения.числа.приведений.типов.(см .главу.12)
Познакомившись с упаковкой, перейдем к распаковке. Допустим, в другом месте
кода нужно извлечь первый элемент массива
ArrayList
:
Point p = (Point) a[0];
Здесь ссылка (или указатель), содержащаяся в элементе с номером 0 массива
ArrayList
, помещается в переменную
p
значимого типа
Point
. Для этого все поля,
содержащиеся в упакованном объекте
Point
, надо скопировать в переменную
p
значимого типа, находящуюся в стеке потока. CLR выполняет эту процедуру в два
этапа. Сначала извлекается адрес полей
Point
из упакованного объекта
Point
. Этот
процесс называют
распаковкой
(unboxing). Затем значения полей копируются из
кучи в экземпляр значимого типа, находящийся в стеке.
Распаковка
не
является точной противоположностью упаковки. Она гораздо
менее ресурсозатратна, чем упаковка, и состоит только в получении указателя на
исходный значимый тип (поля данных), содержащийся в объекте. В сущности,
указатель ссылается на неупакованную часть упакованного экземпляра, и никакого
копирования при распаковке (в отличие от упаковки) не требуется. Однако вслед
за распаковкой обычно выполняется копирование полей.
Понятно, что упаковка и распаковка/копирование снижают производитель-
ность приложения (в плане как замедления, так и расходования дополнительной
памяти), поэтому нужно знать, когда компилятор сам создает код для выполнения
этих операций, и стараться свести их к минимуму.
При распаковке упакованного значимого типа происходит следующее.
1. Если переменная, содержащая ссылку на упакованный значимый тип, равна
null
, генерируется исключение
NullReferenceException
.
2. Если ссылка указывает на объект, не являющийся упакованным значением
требуемого значимого типа, генерируется исключение
Invalid CastException
1
.
1
CLR также позволяет распаковывать значимые типы в версию этого же типа, поддержи-
вающую присвоение значений null (см. главу 19).
159
Упаковка.и.распаковка.значимых.типов
Из второго пункта следует, что приведенный ниже код
не
работает так, как
хотелось бы:
public static void Main() {
Int32 x = 5;
Object o = x; // Упаковка x; o указывает на упакованный объект
Int16 y = (Int16) o; // Генерируется InvalidCastException
}
Казалось бы, можно взять упакованный экземпляр
Int32
, на который указы-
вает
o
, и привести к типу
Int16
. Однако при распаковке объекта должно быть вы-
полнено приведение к неупакованному типу (в нашем случае — к
Int32
). Вот как
выглядит правильный вариант:
public static void Main() {
Int32 x = 5;
Object o = x; // Упаковка x; o указывает на упакованный объект
Int16 y = (Int16)(Int32) o; // Распаковка, а затем приведение типа
}
Как я уже отмечал, распаковка часто сопровождается копированием полей.
Следующий код на C# демонстрирует, что операции распаковки и копирования
часто работают совместно:
public static void Main() {
Point p;
p.x = p.y = 1;
Object o = p; // Упаковка p; o указывает на упакованный объект
p = (Point) o; // Распаковка o и копирование полей из экземпляра в стек
}
В последней строке компилятор C# генерирует IL-команду для распаков-
ки
o
(получение адреса полей в упакованном экземпляре) и еще одну IL-команду
для копирования полей из кучи в переменную
p
, располагающуюся в стеке.
Теперь посмотрите на следующий пример:
public static void Main() {
Point p;
p.x = p.y = 1;
Object o = p; // Упаковка p; o указывает на упакованный экземпляр
// Изменение поля x структуры Point (присвоение числа 2).
p = (Point) o; // Распаковка o и копирование полей из экземпляра
// в переменную в стеке
p.x = 2; // Изменение состояния переменной в стеке
o = p; // Упаковка p; o ссылается на новый упакованный экземпляр
}
Во второй части примера нужно изменить поле
x
структуры
Point
с 1 на 2. Для
этого выполняют распаковку, копирование полей, изменение поля (в стеке) и упа-
ковку (создающую новый объект в управляемой куче). Вероятно, вы понимаете, что
все эти операции обязательно сказываются на производительности приложения.
160
Do'stlaringiz bilan baham: |