Структура — тип данных, аналогичный классу, но имеющий ряд важных отличий от него:
□ структура является значимым, а не ссылочным типом данных, то есть экземпляр структуры хранит значения своих элементов, а не ссылки на них, и располагается в стеке, а не в хипе;
□ структура не может участвовать в иерархиях наследования, она может только
реализовывать интерфейсы;
□ в структуре запрещено определять конструктор по умолчанию, поскольку он
определен неявно и присваивает всем ее элементам значения по умолчанию
(нули соответствующего типа);
□ в структуре запрещено определять деструкторы, поскольку это бессмысленно.
ПРИМЕЧАНИЕ
Строго говоря, любой значимый тип С# является структурным.
Отличия от классов обусловливают область применения структур: типы данных, имеющие небольшое количество полей, с которыми удобнее работать как со значениями, а не как со ссылками. Накладные расходы на динамическое выделение памяти для небольших объектов могут весьма значительно снизить быстродействие программы, поэтому их эффективнее описывать как структуры, а не как классы.
ПРИМЕЧАНИЕ
С другой стороны, передача структуры в метод по значению требует и дополнительного времени, и дополнительной памяти.
Синтаксис структуры:
[ атрибуты ] [ спецификаторы ] struct имя_структуры [: интерфейсы ]
тело_структуры [; ]
Спецификаторы структуры имеют такой же смысл, как и для класса, причем из спецификаторов доступа допускаются только public, internal и private (последний — только для вложенных структур).
Интерфейсы, реализуемые структурой, перечисляются через запятую. Тело структуры может состоять из констант, полей, методов, свойств, событий, индексаторов, операций, конструкторов и вложенных типов. Правила их описания и использования аналогичны соответствующим элементам классов, за исключением некоторых отличий, вытекающих из упомянутых ранее: □ поскольку структуры не могут участвовать в иерархиях, для их элементов не
могут использоваться спецификаторы protected и protected internal; □ структуры не могут быть абстрактными (abstract), к тому же по умолчанию
они бесплодны (sealed);
□ методы структур не могут быть абстрактными и виртуальными;
□ переопределяться (то есть описываться со спецификатором override) могут
только методы, унаследованные от базового класса object;
□ параметр this интерпретируется как значение, поэтому его можно использовать для ссылок, но не для присваивания;
□ при описании структуры нельзя задавать значения полей по умолчанию1 -
это будет сделано в конструкторе по умолчанию, создаваемом автоматически
(конструктор присваивает значимым полям структуры нули, а ссылочным -
значение null).
В листинге 9.8 приведен пример описания структуры, представляющей комплексное число. Для экономии места из всех операций приведено только описание сложения. Обратите внимание на перегруженный метод ToString: он позволяет выводить экземпляры структуры на консоль, поскольку неявно вызывается в методе Console.WriteLine. Использованные в методе спецификаторы формата описаны в приложении.
Листинг 9.8. Пример структуры
using System;
namespace ConsoleApplication1
{
struct Complex
{
public double re, im;
public Complex(double re_, double im_)
{
re = re_; im = im_; // можно использовать this.re. this.im
}
public static Complex operator +(Complex a, Complex b)
{
return new Complex(a.re + b.re, a.im + b.im);
}
public override string ToString()
{
return (string.Format("({0, 2:0.##}; {1,2:0.##})", re, im));
}
class Class1
{
static void Main()
{
Complex a = new Complex(1.2345, 5.6);
Console.WriteLine("а = " + а);
Complex b;
b.re = 10; b.im = 1;
Console.WriteLine("b = " + b);
Complex с = new Complex();
Console.WriteLine("c = " + с);
с = a + b;
Console.WriteLine("c = " + с);
}
}
}
}
Результат работы программы:
а = (1.23;5.6)
b = (10; 1) с = (0; 0) с = (11.23; 6.6)
При выводе экземпляра структуры на консоль выполняется упаковка, то есть неявное преобразование в ссылочный тип. Упаковка (это понятие было введено в разделе «Упаковка и распаковка», см. с. 36) применяется и в других случаях, когда структурный тип используется там, где ожидается ссылочный, например, при преобразовании экземпляра структуры к типу реализуемого ею интерфейса. При обратном преобразовании — из ссылочного типа в структурный — выполняется распаковка.
Присваивание структур имеет, что естественно, значимую семантику, то есть при присваивании создается копия значений полей. То же самое происходит и при передаче структур в качестве параметров по значению. Для экономии ресурсов ничто не мешает передавать структуры в методы по ссылке с помощью ключевых слов ref или out.
Особенно значительный выигрыш в эффективности можно получить, используя массивы структур вместо массивов классов. Например, для массива из 100 экземпляров класса создается 101 объект, а для массива структур — один объект. Пример работы с массивом структур, описанных в предыдущем листинге:
Complex [] mas = new Complex[4];
for (int i = 0; i < 4; ++i)
{
mas[i].re = i;
mas[i].im = 2 * i;
}
foreach (Complex elem in mas)
Console.WriteLine(elem);
Если поместить этот фрагмент вместо тела метода Main в листинге 9.5, получим следующий результат:
(0; 0)
(1; 2)
(2; 4)
(3; 6)