Структуры
В прошлом уроке значение копировалось, как ксерокопия, а объект получал ещё одну ссылку, как общий документ. Встроенные значимые типы хранят одно значение: число, символ, true или false. А если значение состоит из нескольких частей, как точка на плане из двух координат, и копироваться должно целиком, как лист на ксероксе?
Для этого в C# есть структура — свой значимый тип, объявленный словом struct. Она похожа на класс: у неё тоже есть поля, и объявляют её после инструкций верхнего уровня.
Point p = new Point();
p.X = 3;
p.Y = 4;
Console.WriteLine(p.X + " " + p.Y); // 3 4
struct Point
{
public int X;
public int Y;
}Давайте разберём пример по строкам:
struct Pointобъявляет структуру с двумя полями,XиY.new Point()создаёт точку, у которой все поля равны нулю. Для структурыnewне создаёт объект в куче.p.X = 3иp.Y = 4записывают координаты прямо в переменнуюp: ссылки на объект здесь нет.- Последняя строка печатает
3 4.
Сами int, double и bool в .NET тоже объявлены как структуры. Поэтому ваша структура копируется так же, как встроенные значимые типы.
Присваивание копирует все поля
Присваивание структуры делает ксерокопию сразу всех её полей. После Point b = a; у b свои X и Y, и запись в b.X не трогает a. С классом та же строка дала бы второе имя одному объекту.
В виджете обе версии идут друг за другом: структура Point и класс Spot с теми же полями. У переменной структуры значение записано прямо в рамке, а у переменной класса стрелка → ведёт к объекту. Нажимайте «Следующий шаг» и читайте подпись: она говорит, общие ли данные у имён.
1Point a = new Point();2a.X = 1;3a.Y = 2;4Point b = a;5b.X = 10;6Console.WriteLine(a.X + " " + b.X);7Spot c = new Spot();8c.X = 1;9c.Y = 2;10Spot d = c;11d.X = 10;12Console.WriteLine(c.X + " " + d.X);13 14struct Point { public int X; public int Y; }15class Spot { public int X; public int Y; }
new Point() создаёт точку с нулями прямо в переменной a.
Разница видна в двух строках вывода:
1 10: у структурaиbсвои поля, и запись вb.Xне дошла доa;10 10:cиdведут к одному объекту, и запись черезdвидна черезc.
Здесь аналогия с ксерокопией неточна. В жизни можно скопировать только нужную страницу. Структура же копируется целиком: все поля разом, даже если потом вы меняете одно.
struct или class
Этот вопрос часто задают на собеседовании: чем struct отличается от class? Главная часть ответа — копирование. Присваивание структуры копирует все поля, а присваивание класса — только ссылку.
Отсюда и выбор между ними:
- структура подходит для небольшого значения из нескольких полей, которое естественно копировать: точка, цвет, сумма в рублях;
- класс подходит для того, что должно быть общим для всех, кто с ним работает: пользователь, заказ, банковский счёт;
- копия большой структуры обходится дороже копии ссылки, поэтому структуры делают маленькими.
Структура в параметре метода
Метод получает копию аргумента, и для структуры это копия всех полей. Поэтому метод без ref не может сдвинуть вашу точку:
Point start = new Point();
start.X = 3;
Shift(start);
Console.WriteLine(start.X); // 3
void Shift(Point point)
{
point.X = point.X + 10;
}Разберём, что происходит:
Shift(start)копирует всю точку в параметрpoint.point.X = point.X + 10меняет копию: у неёXстановится13.- Метод завершается, копия исчезает, и
start.Xпечатает3.
Чтобы метод изменил саму структуру, её передают с ref, как переменную int.
Частая ошибка
Точку в списке попробовали изменить по индексу:
List<Point> route = [new Point()];
route[0].X = 5; // ✗error CS1612: Cannot modify the return value of 'List<Point>.this[int]' because it is not a variableПрограмма не компилируется. route[0] у списка возвращает копию точки, а не саму точку из списка. Запись в поле копии сразу бы пропала, поэтому компилятор её запрещает. this[int] в сообщении — это доступ по индексу в квадратных скобках.
Исправление — прочитать точку в переменную, изменить её и записать обратно:
List<Point> route = [new Point()];
Point first = route[0];
first.X = 5;
route[0] = first;
Console.WriteLine(route[0].X); // 5У массива такой проблемы нет: points[0].X = 5 меняет точку прямо в массиве, потому что элемент массива — переменная, а не копия.
Главное
structобъявляет свой значимый тип: значение хранится прямо в переменной, без ссылки на объект.- Присваивание структуры копирует все поля, присваивание класса — только ссылку.
- Метод получает копию структуры и без
refне меняет переменную вызывающего кода. list[0]возвращает копию структуры, поэтомуlist[0].X = 5дляListне компилируется.