Структуры C#

Структуры в C# относятся к типам значений. Это значит что структуры, как и другие значимые типы, производные от System.ValueType, размещаются в стеке, и, следовательно, время жизни структур предсказуемо. Они уничтожаются сразу же как только выходят за пределы видимости блока кода, в котором они были созданы.

Структуры в C# определяются при помощи ключевого слова struct:

struct Struct1
{
    // члены структуры ( поля, методы, свойства, события )
}

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

Члены структуры не могут иметь модификаторов доступа abstract, virtual и protected, связанных с механизмами наследования.

Пример структуры C# со статическим конструктором без аргументов:

using System;

struct Program {
    private static int staticInt;

    // Статический конструктор структуры Program
    static Program() {
        // Инициализация статического поля staticInt
        staticInt = 1;
    }

    static void Main() {
        Console.WriteLine(staticInt);
        Console.ReadKey();        
    }
}

Структуры, также как и классы, могут реализовывать произвольное количество интерфейсов.

С точки зрения исполняющей среды структуры являются классами, а не особым типом данных. Однако, в отличие от других классов, структуры особым образом обрабатываются компилятором. Так например к каждой структуре автоматически добавляется модификатор sealed, который запрещает наследование от данного типа.

Так, например, следующее определение структуры:

struct Point {
    public int X;
    public int Y;
}

Преобразуется компилятором в следующий IL-код:

.class private sealed sequential ansi beforefieldinit
  Point extends [mscorlib]System.ValueType {

  .field public int32 X
  .field public int32 Y
}

Из этого фрагмента видно, что компилятор заменил слово struct на class, добавил к определению структуры модификатор sealed (запечатанный), а также наследование от System.ValueType.


Инициализация структур

Перед использованием структура должна быть инициализирована. Инициализация структуры осуществляется с помощью ключевого слова new() или вручную при помощи установки значений всех полей структуры. Рассмотрим следующий пример:

using System;

struct TestStruct {
    public short First;
    public byte Second;
}

class Program {
    static void Main() {
        TestStruct s1 = new TestStruct(); // Инициализация при помощи ключевого слова new()
        Console.WriteLine( s1 );

        TestStruct s2;        
        Console.WriteLine( s2 ); // Ошибка, нужна инициализация структуры

        // Инициализация структуры путем установки значений ее полей
        s2.First = 0;
        s2.Second = 0;
        Console.WriteLine( s2 ); // Здесь ошибки нет

        Console.ReadKey();
    }
}

Поля структуры не могут быть инициализированы во время создания. Так например следующий код вызывает ошибку:

struct Point {
    public int X = 1; // Ошибка!
    public int Y;
}

Размещение полей структуры в памяти

Рассмотрим следующий пример кода:

struct TestStruct {
    public short First;
    public byte Second;
}

class Program {
    static void Main() {
        TestStruct s = new TestStruct();

        Console.WriteLine( "Размер структуры в байтах: " + Marshal.SizeOf( s ) );
        Console.ReadKey();
    }
}

В структуре TestStruct объявлены два поля: First типа short ( 2 байта ) и Second тип byte ( 1 байт ). В методе Main() создается экземпляр этой структуры и в консоль выводится ее размер в байтах. Можно было бы подумать, что размер структуры будет равен 3 байтам, сумме размеров полей структуры. Однако, если запустить предыдущий пример, в консоль выводится размер в 4 байта.

Вывод размера структуры
Вывод размера структуры

Такой результат получился благодаря тому, что среда CLR по умолчанию уместить элементы структуры в блоках размер которых равен размеру самого большого элемента в структуре. Так как в приведенной структуре размер самого большего поля – First – равен 2 байтам, то все поля размещаются в блоках по 2 байта.

Выравнивание полей структуры
Выравнивание полей структуры

На рисунке первые два байта были заняты полем First ( short ), третий байт полем Second ( byte ). В конце был добавлен еще один байт – “заполнитель” – для выравнивания до 2-х байт. Он не содержит никакой полезной информации и служит только для удобства управления структурой средой CLR.

Наличие “заполнителей” зависит от порядка следования полей в структуре. Рассмотрим две похожих структуры:

using System;
using System.Runtime.InteropServices;

struct TestStruct1 {
    public short First;
    public byte Second;
    public byte Third;
}

struct TestStruct2 {
    public byte Third;
    public short First;
    public byte Second;
}

class Program {
    static void Main() {
        TestStruct1 s1 = new TestStruct1();
        TestStruct2 s2 = new TestStruct2();

        Console.WriteLine( "Размер структуры TestStruct1 в байтах: " + Marshal.SizeOf( s1 ) );
        Console.WriteLine( "Размер структуры TestStruct2 в байтах: " + Marshal.SizeOf( s2 ) );
        Console.ReadKey();
    }
}

Результат выполнения этой программы будет следующим:

Разница в размерах объясняется выравниванием полей в структурах. Следующий рисунок показывает характер размещения структур в памяти:

Разница в выравнивании в зависимости от порядка размещения полей в структуре
Разница в выравнивании в зависимости от порядка размещения полей в структуре

Настройка расположения полей структуры с помощью атрибута StructLayout

Часто поведения структур по умолчанию вполне достаточно, однако может понадобиться более детальная настройка расположения полей и выравнивания структуры. Это можно сделать с помощью атрибута StructLayout из пространства имен System.Runtime.InteropServices.

Атрибут StructLayout позволяет произвести детальную настройку полей структуры. Конструктор этого атрибута должен содержать одно из значений перечисления LayoutKind, допустимые значения которого представлены в таблице ниже:

Поля перечисления LayoutKind:

Название поляЗначениеОписание
Auto3Среда выполнения автоматически определяет порядок расположения полей в структуре и границу выравнивания. Объекты, определенные с помощью этого члена перечисления, не могут быть использованы в неуправляемом коде. Попытка сделать это вызовет исключение.
Explicit2Точное положение каждого поля в неуправляемой памяти контролируется явно, в зависимости от настройки поля Pack. Каждое поле должно использовать атрибут FieldOffset, чтобы указать положение этого поля в структуре.
Sequential0Элементы объекта располагаются последовательно в том порядке, в котором они отображаются при экспорте в неуправляемую память. Элементы разложены в соответствии с упаковкой, указанной в поле Pack, и могут быть несмежными.

По умолчанию структурам присваивается значение атрибута Sequential, что видно если посмотреть на IL-код структуры:

// по умолчанию структуре присвоен модификатор sequential
.class private sealed sequential ansi beforefieldinit
  TestStruct extends [mscorlib]System.ValueType {

  .field public int16 First
  .field public unsigned int8 Second
}

Атрибут StructLayout имеет следующие свойства:

ПолеОписание
PackУказывает выравнивание полей в памяти. Поле Pack может принимать одно из следующих значений: 0, 1, 2, 4, 8, 16, 32, 64, 128
SizeУказывает размер структуры ( или класса )
CharSetУказывает как строковые поля данных в структуре ( или классе ) преобразовываться в неуправляемые структуры LPWSTR или LPSTR (по умолчанию)

Для структуры TestStruct мы можем указать значение поля Pack=1 для выравнивания по границе 1 байта, тогда размер структуры будет равен 3, как и ожидалось:

using System;
using System.Runtime.InteropServices;

[StructLayout( LayoutKind.Sequential, Pack = 1 )]
struct TestStruct {
    public short First;
    public byte Second;
}

class Program {
    static void Main() {
        TestStruct s = new TestStruct();

        Console.WriteLine( "Размер структуры в байтах: " + Marshal.SizeOf( s ) );
        Console.ReadKey();
    }
}

// Вывод: Размер структуры в байтах: 3
avatar
5000
  Подписаться  
Уведомление о