Бөлісу құралы:


Структуры C#

Подсказка

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

Есть опыт на другом языке? Структуры C# — это типы значений, аналогичные структурам в C++ или Swift, но они размещаются в управляемой куче при упаковке и поддерживают интерфейсы, конструкторы и методы. Просмотрите раздел только для чтения структур для C#-специфических паттернов. Инструкции записей см. в разделе "Записи".

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

Объявление структуры

Определите структуру с ключевым словом struct . Структуру можно содержать поля, свойства, методы и конструкторы, как класс:

struct Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public readonly double DistanceTo(Point other)
    {
        var dx = X - other.X;
        var dy = Y - other.Y;
        return Math.Sqrt(dx * dx + dy * dy);
    }

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

В Point структуре хранятся два double значения и предоставляется метод для вычисления расстояния между двумя точками. Метод DistanceTo помечен readonly , так как он не изменяет состояние структуры. Этот шаблон рассматривается в элементах чтения.

Семантика значений

Структуры — это типы значений. Назначение копирует данные, поэтому каждая переменная содержит собственную независимую копию:

var p1 = new Point { X = 3, Y = 4 };
var p2 = p1; // copies the data
p2.X = 10;

Console.WriteLine(p1); // (3, 4)  — p1 is unchanged
Console.WriteLine(p2); // (10, 4) — only p2 was modified

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

Конструкторы структуры

Конструкторы в структурах можно определить так же, как и в классах. Структуры могут иметь конструкторы без параметров , которые задают пользовательские значения по умолчанию. Термин "конструктор без параметров" отличает экземпляр, созданный с new вызовом (который выполняет логику вашего конструктора) от экземпляра по умолчанию, созданного с default помощью выражения (который инициализирует все поля нулями):

struct ConnectionSettings
{
    public string Host { get; set; }
    public int Port { get; set; }
    public int MaxRetries { get; set; }

    public ConnectionSettings()
    {
        Host = "localhost";
        Port = 8080;
        MaxRetries = 3;
    }
}

Конструктор без параметров выполняется при использовании new без аргументов. Выражение default обходит конструктор и задает всем полям значения по умолчанию (0, null, false). Имейте в виду разницу:

var custom = new ConnectionSettings();
Console.WriteLine($"{custom.Host}:{custom.Port} (retries: {custom.MaxRetries})");
// localhost:8080 (retries: 3)

var defaults = default(ConnectionSettings);
Console.WriteLine($"{defaults.Host ?? "(null)"}:{defaults.Port} (retries: {defaults.MaxRetries})");
// (null):0 (retries: 0)

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

struct GameTile
{
    public int Row { get; set; }
    public int Column { get; set; }
    public bool IsBlocked { get; set; }

    public GameTile(int row, int column)
    {
        Row = row;
        Column = column;
        // IsBlocked is automatically initialized to false
    }
}
var tile = new GameTile(2, 5);
Console.WriteLine($"Tile ({tile.Row}, {tile.Column}), blocked: {tile.IsBlocked}");
// Tile (2, 5), blocked: False

Свойство IsBlocked не назначается в конструкторе, поэтому компилятор задает для него значение false (значение по умолчанию для bool). Эта функция уменьшает количество шаблонного кода в конструкторах, которые должны инициализировать только несколько полей.

Структуры только для чтения и члены только для чтения

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

readonly struct Temperature
{
    public double Celsius { get; }

    public Temperature(double celsius) => Celsius = celsius;

    public double Fahrenheit => Celsius * 9.0 / 5.0 + 32.0;

    public override string ToString() => $"{Celsius:F1}°C ({Fahrenheit:F1}°F)";
}
var temp = new Temperature(100);
Console.WriteLine(temp); // 100.0°C (212.0°F)
// temp.Celsius = 50; // Error: property is read-only

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

struct Velocity
{
    public double X
    {
        readonly get;
        set;
    }

    public double Y
    {
        readonly get;
        set;
    }

    public readonly double Speed => Math.Sqrt(X * X + Y * Y);

    public readonly override string ToString() => $"({X}, {Y}) speed={Speed:F2}";
}
var v = new Velocity { X = 3, Y = 4 };
Console.WriteLine(v.Speed); // 5
Console.WriteLine(v);       // (3, 4) speed=5.00
v.X = 6;
Console.WriteLine(v.Speed); // 7.211...

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

Когда следует использовать структуры

Используйте структуру, когда ваш тип:

  • Представляет одно значение или небольшую группу связанных значений (примерно 16 байтов или меньше).
  • Имеет семантику значений— два экземпляра с одинаковыми данными должны быть равными.
  • Это в первую очередь контейнер данных, а не модель поведения.
  • Не требуется наследование от базового типа (структуры не могут наследовать от других структур или классов, но они могут реализовывать интерфейсы).

Более широкое сравнение, включающее классы, записи, кортежи и интерфейсы, см. в разделе "Выбор типа".

См. также