Введение в С# для программистов (A programmer's Introduction to C#)

Автор - Eric Gunnerson
перевод - Трубецкой А.


На главную
Содержание
Глава 8
Глава 10

Глава 9: Структуры (Типы по значению)

Обзор

Для реализации большинства объектов в C# используются классы. Однако иногда бывает полезно создать объект, который ведет себя как один из встроенных типов, т.е. быстрее размещается в памяти и занимает меньше места. В этом случае используется тип по значению, который объявляется с помощью ключевого слова struct.
Работа со структурами напоминает работу с классами, но с некоторыми ограничениями. Структуры нельзя наследовать от любого другого типа (хотя они неявно наследуются от класса object). Другие классы также не могут наследоваться от структуры.


Структура Point

В графической системе тип по значению можно использовать, чтобы инкапсулировать графическую точку. Такой тип может быть объявлен следующим образом:
struct Point {
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public override string ToString() {
return(String.Format("({0}, {1})", x, y));
}
public int x;
public int y;
}
class Test {
public static void Main() {
Point start = new Point(5, 5);
Console.WriteLine("Start: {0}", start);
}
}
Поля x и y являются открытыми и к ним можно обращаться. В функции Main() объект типа Point создается с помощью ключевого слова new. Для типов по значению new создает объект в стеке и затем вызывает соответствующий конструктор. Напомним, что в отличие от типов по значению, которые находятся в стеке, типы по ссылке размещаются в куче.
Вызов метода Console.WriteLine() кажется немного странным. Если объект размещен в стеке, то как может работать этот вызов?


Упаковка и распаковка (boxing and unboxing)

В C# и .NET Runtime имеется интересная особенность, которая позволяет сделать типы по значению похожими на типы по ссылке, и эта особенность называется упаковкой (boxing). Реализуется она довольно просто. При вызове Console.WriteLine() компилятор ищет способ преобразовать start в object, потому что второй параметр для WriteLine() должен быть object. Для типа по ссылке (то есть для класса) это преобразование элементарно, потому что object - базовый класс для всех остальных классов. Компилятор просто передает ссылку, которая указывает на экземпляр класса, как ссылку на object.
Однако для типов по значению не существует ссылочного эквивалента, поэтому компилятор создает для Point "упаковку" ссылочного типа, помечает ее как содержащую экземпляр типа Point (чтобы среда времени выполнения знала, какой тип был упакован) и копирует в нее значение Point. Упаковка размещается в куче (heap). Теперь мы имеем дело с ссылочным типом и можем работать с ним как с object. Далее эта ссылка передается функции WriteLine(), которая вызывает функцию ToString() для упакованной переменной Point, и на консоль выводится:

Start: (5, 5)

Упаковка происходит автоматически каждый раз, когда тип по значению используется там, где требуется (или возможно) использование object.

Упакованная переменная преобразуется обратно в тип по значению с помощью распаковки (unboxing).
int v = 123;
object o = v; // упаковка переменной типа int в ссылочный тип
int v2 = (int) o; // распаковка, в результате которой опять получаем int
Когда объекту o присваивается значение v, целочисленное значение автоматически упаковывается. В следующей строке переменная типа int вновь распаковывается.
Этот код представлен на рисунке 9-1.

Упаковка и распаковка
Рисунок 9-1. Упаковка и распаковка типа по значению.

Получаемый при распаковке тип переменной должен точно соответствовать тому типу, который был упакован. Преобразование в другой тип автоматически не производится (даже если это совместимые типы):
object o = 15;
short s = (short) o; // ошибка: o не содержит short
short t = (short)(int) o; // эта строка будет работать


Структуры и конструкторы
В структурах конструкторы ведут себя немного иначе, чем в классах. Прежде чем использовать экземпляр класса, необходимо создать его с помощью ключевого слова new. Если new не используется, то экземпляр класса не будет создан и ссылка на объект будет нулевой.
Однако доступ к структурам не осуществляется с помощью ссылок. Если для структуры не вызывается new, то создается экземпляр структуры, у которого все поля имеют нулевое значение. В некоторых случаях можно использовать такой экземпляр без дальнейшей инициализации.

В связи с этим важно отметить, что состояние, при котором переменная полностью заполнена нулями, является допустимым начальным состоянием для всех типов по значению.

Конструктор по умолчанию (конструктор без параметров) мог бы устанавливать отличные от нуля значения полей при создании переменной, что могло бы привести к ее непредсказуемому поведению. Поэтому .NET Runtime запрещает создавать конструктор без параметров для структуры. Компилятор всегда сам генерирует такой конструктор по умолчанию и его нельзя изменить.


Рекомендации
Структуры следует использовать только для типов, которые в действительности являются только частью данных, - для типов, которые могли бы быть использованы таким же образом, как и встроенные типы.

Если же более сложные типы и можно реализовать в виде типов по значению, то не стоит этого делать, поскольку подобная семантика может быть неожиданной для пользователя. Пользователь часто ожидает, что ссылка на переменную может быть нулевой, что невозможно для типов по значению.


На главную
Глава 8
Глава 10


Rambler's Top100
Хостинг от uCoz