Глава.5 .Примитивные,.ссылочные.и.значимые.типы
private readonly Int16 m_x;
...
}
Атрибут
StructLayoutAttribute
также позволяет явно задать смещение для
всех полей, передав в конструктор
LayoutKind.Explicit
. Затем можно применить
атрибут
System.Runtime.InteropServices.FieldOffsetAttribute
ко всем полям
путем передачи конструктору этого атрибута значения типа
Int32
, определяющего
смещение (в байтах) первого байта поля от начала экземпляра. Явное размещение
обычно используется для имитации того, что в неуправляемом коде на C/C++
называлось
объединением
(union), то есть размещения нескольких полей с одного
смещения в памяти, например:
using System;
using System.Runtime.InteropServices;
// Разработчик явно задает порядок полей в значимом типе
[StructLayout(LayoutKind.Explicit)]
internal struct SomeValType {
[FieldOffset(0)]
private readonly Byte m_b; // Поля m_b и m_x перекрываются
[FieldOffset(0)]
private readonly Int16 m_x; // в экземплярах этого класса
}
Не допускается определение типа, в котором перекрываются ссылочный и значи-
мый типы. Можно определить тип, в котором перекрываются несколько значимых
типов, однако все перекрывающиеся байты должны быть доступны через открытые
поля, чтобы обеспечить верификацию типа.
Упаковка и распаковка значимых типов
Значимые типы «легче» ссылочных: для них не нужно выделять память в управ-
ляемой куче, их не затрагивает сборка мусора, к ним нельзя обратиться через
указатель. Однако часто требуется получать ссылку на экземпляр значимого типа,
например если вы хотите сохранить структуры
Point
в объекте типа
ArrayList
(определен в пространстве имен
System.Collections
). В коде это выглядит при-
мерно следующим образом:
// Объявляем значимый тип
struct Point {
public Int32 x, y;
}
public sealed class Program {
public static void Main() {
157
Упаковка.и.распаковка.значимых.типов
ArrayList a = new ArrayList();
Point p; // Выделяется память для Point (не в куче)
for (Int32 i = 0; i < 10; i++) {
p.x = p.y = i; // Инициализация членов в нашем значимом типе
a.Add(p); // Упаковка значимого типа и добавление
// ссылки в ArrayList
}
...
}
}
В каждой итерации цикла инициализируются поля значимого типа
Point
, после
чего
Point
помещается в
ArrayList
. Задумаемся, что же помещается в
ArrayList
:
сама структура
Point
, адрес структуры
Point
или что-то иное? За ответом обратимся
к методу
Add
типа
ArrayList
и посмотрим описание его параметра. В данном случае
прототип метода
Add
выглядит следующим образом:
public virtual Int32 Add(Object value);
Отсюда видно, что в параметре
Add
должен передаваться тип
Object
, то есть
ссылка (или указатель) на объект в управляемой куче. Однако в примере я передаю
переменную
p
, имеющую значимый тип
Point
. Чтобы код работал, нужно преобразо-
вать значимый тип
Point
в объект из управляемой кучи и получить на него ссылку.
Для преобразования значимого типа в ссылочный служит
упаковка
(boxing).
При упаковке экземпляра значимого типа происходит следующее.
1. В управляемой куче выделяется память. Ее объем определяется длиной значи-
мого типа и двумя дополнительными членами — указателем на типовой объект
и индексом блока синхронизации. Эти члены необходимы для всех объектов
в управляемой куче.
2. Поля значимого типа копируются в память, только что выделенную в куче.
3. Возвращается адрес объекта. Этот адрес является ссылкой на объект, то есть
значимый тип превращается в ссылочный.
Компилятор C# создает IL-код, необходимый для упаковки экземпляра значи-
мого типа, автоматически, но вы должны понимать, что происходит «за кулисами»
и помнить об опасности «распухания» кода и снижения производительности.
В предыдущем примере компилятор C# обнаружил, что методу, требующему
ссылочный тип, в параметре передается значимый тип, и автоматически создал код
для упаковки объекта. Вследствие этого поля экземпляра
p
значимого типа
Point
в период выполнения копируются во вновь созданный в куче объект
Point
. Полу-
ченный адрес упакованного объекта
Point
(теперь это ссылочный тип) передается
методу
Add
. Объект
Point
остается в куче до очередной уборки мусора. Перемен-
ную
p
значимого типа
Point
можно использовать повторно, так как
ArrayList
ничего
о ней не знает. Заметьте: время жизни упакованного значимого типа превышает
время жизни неупакованного значимого типа.
Do'stlaringiz bilan baham: |