Поделиться через


Типы структур (справочник по C#)

Тип структуры представляет собой тип значения, который может инкапсулировать данные и связанные функции. Для определения типа структуры используется ключевое слово struct:

public struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; }
    public double Y { get; }

    public override string ToString() => $"({X}, {Y})";
}

Сведения о ref struct типах и readonly ref struct типах см. в статье о типах структур ссылок .

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

Как правило, типы структуры используются для проектирования небольших ориентированных на данные типов, которые предоставляют минимум поведения или не предоставляют его вовсе. Например, платформа .NET использует типы структуры для представления числа (как целого, так и вещественного), логического значения, символа Юникода, экземпляра времени. Если вы сконцентрированы на поведении типа, рекомендуется определить класс. Типы классов имеют семантики ссылок. То есть переменная типа класса содержит ссылку на экземпляр этого типа, а не сам экземпляр.

Так как типы структур имеют семантику значений, рекомендуется определить неизменяемые типы структур.

Структура readonly

Модификатор используется readonly для объявления о том, что тип структуры неизменяем. Все элементы данных структуры readonly должны быть доступны только для чтения:

  • Любое объявление поля должно иметь readonly модификатор.
  • Любое свойство, включая автоматически реализованные, должно быть доступно только для чтения или init только для чтения. Обратите внимание, что средства установки только для инициализации доступны только из C# версии 9.

Это гарантирует, что ни один из элементов структуры readonly не изменит состояние структуры. Это означает, что другие члены экземпляра, кроме конструкторов, неявно readonly.

Примечание.

В структуре readonly элемент данных изменяемого ссылочного типа по-прежнему может изменять свое собственное состояние. Например, вы не можете заменить экземпляр List<T>, но можете добавить в него новые элементы.

Следующий код определяет структуру readonly с помощью наборов свойств только для инициализации:

public readonly struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; init; }
    public double Y { get; init; }

    public override string ToString() => $"({X}, {Y})";
}

Члены экземпляров readonly

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

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

Как правило, модификатор readonly применяется к следующим типам элементов экземпляров.

  • Методы.

    public readonly double Sum()
    {
        return X + Y;
    }
    

    Можно также применить модификатор readonly к методам, переопределяющим методы, объявленные в System.Object.

    public readonly override string ToString() => $"({X}, {Y})";
    
  • Свойства и индексаторы.

    private int counter;
    public int Counter
    {
        readonly get => counter;
        set => counter = value;
    }
    

    Если необходимо применить модификатор readonly к методам доступа свойства или индексатора, примените его в объявлении свойства или индексатора.

    Примечание.

    Компилятор объявляет get метод доступа автоматически реализованного свойства readonlyнезависимо от наличия readonly модификатора в объявлении свойств.

    Модификатор можно применить readonly к свойству или индексатору init с помощью метода доступа:

    public readonly double X { get; init; }
    

Модификатор можно применить readonly к статическим полям типа структуры, но не к другим статическим элементам, таким как свойства или методы.

Компилятор может использовать модификатор readonly для оптимизации производительности. Дополнительные сведения см. в разделе "Избегание выделения".

Обратимое изменение

Начиная с C# 10 можно использовать выражение with для создания копии экземпляра с типом структуры, в котором изменяются указанные свойства и поля. Как показано в следующем примере, для указания элементов для изменения и их новых значений используется синтаксис инициализатора объектов.

public readonly struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; init; }
    public double Y { get; init; }

    public override string ToString() => $"({X}, {Y})";
}

public static void Main()
{
    var p1 = new Coords(0, 0);
    Console.WriteLine(p1);  // output: (0, 0)

    var p2 = p1 with { X = 3 };
    Console.WriteLine(p2);  // output: (3, 0)

    var p3 = p1 with { X = 1, Y = 4 };
    Console.WriteLine(p3);  // output: (1, 4)
}

Структура record

Начиная с C# 10, можно определить типы структуры записей. Типы записей предоставляют встроенные функции для инкапсулирования данных. Вы можете определить оба record struct типа и readonly record struct типы. Не может быть структурой ref structзаписи. Дополнительные сведения и примеры см. в разделе "Записи".

Встроенные массивы

Начиная с C# 12, можно объявить встроенные struct массивы как тип:

[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBuffer
{
    private char _firstElement;
}

Встроенный массив — это структура, содержащая непрерывный блок элементов N одного типа. Это безопасный код эквивалент фиксированного объявления буфера , доступного только в небезопасном коде. Встроенный struct массив имеет следующие характеристики:

  • Он содержит одно поле.
  • Структура не указывает явный макет.

Кроме того, компилятор проверяет System.Runtime.CompilerServices.InlineArrayAttribute атрибут:

  • Длина должна быть больше нуля (> 0).
  • Целевой тип должен быть структурой.

В большинстве случаев встроенный массив можно получить как к массиву, так и для чтения и записи значений. Кроме того, можно использовать операторы диапазона и индекса .

Существуют минимальные ограничения на тип одного поля встроенного массива. Это не может быть тип указателя:

[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBufferWithPointer
{
    private unsafe char* _pointerElement;    // CS9184
}

но это может быть любой ссылочный тип или любой тип значения:

[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBufferWithReferenceType
{
    private string _referenceElement;
}

Встроенные массивы можно использовать практически с любой структурой данных C#.

Встроенные массивы — это расширенная функция языка. Они предназначены для высокопроизводительных сценариев, где встроенный смежный блок элементов быстрее, чем другие альтернативные структуры данных. Дополнительные сведения о встроенных массивах можно узнать из спецификации компонентов

Инициализация структуры и значения по умолчанию

Переменная struct типа напрямую содержит данные для этого struct. Это создает различие между неинициализированным значением, которое имеет значение по умолчанию и инициализированоstructstruct, которое сохраняет значения, заданные путем его создания. Например, рассмотрим следующий код:

public readonly struct Measurement
{
    public Measurement()
    {
        Value = double.NaN;
        Description = "Undefined";
    }

    public Measurement(double value, string description)
    {
        Value = value;
        Description = description;
    }

    public double Value { get; init; }
    public string Description { get; init; }

    public override string ToString() => $"{Value} ({Description})";
}

public static void Main()
{
    var m1 = new Measurement();
    Console.WriteLine(m1);  // output: NaN (Undefined)

    var m2 = default(Measurement);
    Console.WriteLine(m2);  // output: 0 ()

    var ms = new Measurement[2];
    Console.WriteLine(string.Join(", ", ms));  // output: 0 (), 0 ()
}

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

Наиболее распространенная ситуация, когда значения по умолчанию содержатся в массивах или в других коллекциях, где внутреннее хранилище содержит блоки переменных. В следующем примере создается массив из 30 TemperatureRange структур, каждый из которых имеет значение по умолчанию:

// All elements have default values of 0:
TemperatureRange[] lastMonth = new TemperatureRange[30];

Все поля-члены структуры должны быть определенно назначены при создании, так как struct типы напрямую хранят свои данные. Значение default структуры определенно присваивало всем полям 0. Все поля должны быть определенно назначены при вызове конструктора. Вы инициализируете поля с помощью следующих механизмов:

  • Вы можете добавить инициализаторы полей в любое поле или автоматически реализованное свойство.
  • Вы можете инициализировать любые поля или автоматические свойства в тексте конструктора.

Начиная с C# 11, если вы не инициализируете все поля в структуре, компилятор добавляет код в конструктор, который инициализирует эти поля в значение по умолчанию. Компилятор выполняет свой обычный анализ определенного назначения. Все поля, к которым обращается перед назначением, или не назначены, когда конструктор завершает выполнение, назначаются значения по умолчанию перед выполнением текста конструктора. Если this доступ осуществляется до назначения всех полей, то структуру инициализируется значением по умолчанию перед выполнением текста конструктора.

public readonly struct Measurement
{
    public Measurement(double value)
    {
        Value = value;
    }

    public Measurement(double value, string description)
    {
        Value = value;
        Description = description;
    }

    public Measurement(string description)
    {
        Description = description;
    }

    public double Value { get; init; }
    public string Description { get; init; } = "Ordinary measurement";

    public override string ToString() => $"{Value} ({Description})";
}

public static void Main()
{
    var m1 = new Measurement(5);
    Console.WriteLine(m1);  // output: 5 (Ordinary measurement)

    var m2 = new Measurement();
    Console.WriteLine(m2);  // output: 0 ()

    var m3 = default(Measurement);
    Console.WriteLine(m3);  // output: 0 ()
}

Каждый struct имеет public конструктор без параметров. При написании конструктора без параметров он должен быть открытым. Если структура объявляет инициализаторы полей, она должна явно объявить конструктор. Этот конструктор не должен быть параметрным. Если структура объявляет инициализатор полей, но никаких конструкторов, компилятор сообщает об ошибке. Любой явно объявленный конструктор (с параметрами или без параметров) выполняет все инициализаторы полей для этой структуры. Все поля без инициализатора полей или назначения в конструкторе задаются значением по умолчанию. Дополнительные сведения см. в примечании к предложению новой возможности в разделе Конструкторы структур без параметров.

Начиная с C# 12 struct типы могут определять основной конструктор в рамках объявления. Основные конструкторы предоставляют краткий синтаксис для параметров конструктора, которые можно использовать во всем тексте struct в любом объявлении члена для этой структуры.

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

public static class StructWithoutNew
{
    public struct Coords
    {
        public double x;
        public double y;
    }

    public static void Main()
    {
        Coords p;
        p.x = 3;
        p.y = 4;
        Console.WriteLine($"({p.x}, {p.y})");  // output: (3, 4)
    }
}

В случае встроенных типов значения используйте соответствующие литералы, чтобы указать значение типа.

Ограничения при проектировании типа структуры

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

  • Тип структуры не может наследовать от другого типа класса или структуры и не может быть базовым для класса. Однако тип структуры может реализовывать интерфейсы.
  • Вы не можете объявить метод завершения в типе структуры.
  • До C# 11 конструктор типа структуры должен инициализировать все поля экземпляра типа.

Передача переменных типа структуры по ссылке

При передаче переменной типа структуры в метод в качестве аргумента или возврате значения типа структуры из метода копируется весь экземпляр типа структуры. Передача по значению может повлиять на производительность кода в сценариях высокой производительности, которые включают большие типы структур. Копирования значений можно избежать, передав переменную типа структуры по ссылке. refИспользуйте модификаторы параметров метода , outref readonly inчтобы указать, что аргумент должен передаваться по ссылке. Чтобы возвратить результат метода по ссылке, используйте ref returns. Дополнительные сведения см. в разделе "Избегание выделения".

Ограничение struct

Вы можете использовать ключевое слово struct в ограничении struct, чтобы указать, что параметр типа является типом значения, не допускающим значения NULL. Типы структуры и перечисления удовлетворяют ограничению struct.

Преобразования

Для любого типа структуры (за исключением ref struct типов), существуют поля и распаковки преобразований в и из System.ValueType типов.System.Object Существуют упаковка-преобразование и распаковка-преобразование между типом структуры и любым интерфейсом, который он реализует.

Спецификация языка C#

Дополнительные сведения см. в разделе Структуры в спецификации языка C#.

Дополнительные сведения о struct функциях см. в следующих заметках о предложении функций:

См. также