JavaRush /Курсы /C# SELF /record с явным телом ...

record с явным телом и record struct

C# SELF
19 уровень , 3 лекция
Открыта

1. Введение

Позиционный синтаксис record-класса действительно удобен для простых случаев:

public record User(string Name, int Age);
Позиционный record-класс

Но иногда у вас появляется жгучее желание добавить к record дополнительные методы, нестандартные свойства, изменить модификаторы доступа, добавить логику в конструктор (например, валидацию или "автоматическое" преобразование данных). Увы, в позиционной записи это просто некуда добавить! Пора переходить к явному синтаксису тела, если:

  • Вам нужно расширить record методами, свойствами или дополнительной логикой.
  • Необходимо контролировать поведение свойств (сеттера, геттеры, inits, валидация).
  • Требуется создать несколько разных конструкторов для разных способов инициализации.
  • Нужно добавить интерфейсы или реализовать специальные методы.

Строим record с телом

Синтаксис очень похож на класс. Вам знакома эта форма:


public class User
{
    /* ... */
}

Только теперь это record:


public record User
{
    // Явно определенные свойства
    public string Name { get; init; }
    public int Age { get; init; }

    // Дополнительная логика
    public string GetGreeting()
    {
        return $"Привет, меня зовут {Name} и мне {Age} лет!";
    }

    // Кастомный конструктор
    public User(string name, int age)
    {
        Name = name;
        if (age < 0)
            throw new ArgumentException("Age не может быть отрицательным!");
        Age = age;
    }
}

Забавный факт: Если вы определяете свои свойства явно, компилятор не создаёт для вас автоматически свойства из позиционного синтаксиса. Всё явно, всё под контролем.

Смешанный вариант: гибридный синтаксис

C# позволяет сочетать оба мира: вы можете объявить позиционный record и добавить к нему тело:


public record User(string Name, int Age)
{
    public string GetGreeting()
    {
        return $"Я {Name}, мне {Age} лет!";
    }
}
Позиционный record с телом и дополнительными методами

В этом случае свойства Name и Age всё также автоматически создаются с помощью позиционного синтаксиса, а ваши дополнительные методы уютно располагаются внутри тела.

2. Отличия от обычных record — нюансы тела

  • Явный record позволяет полностью контролировать конструкторы, свойства, методы.
  • Вы можете реализовать интерфейсы или добавить собственную логику сравнения, если используются сложные правила идентификации объекта.
  • В отличие от простого record с позиционным синтаксисом, для добавления новых свойств вам теперь достаточно просто объявить их в теле record’а.

// Ошибка новичков!
public record User(string Name, int Age)
{
    public string Name { get; init; } // ← конфликт! Повторное объявление свойства
}

Ошибки новичков: Некоторые студенты пытаются одновременно писать public record User(string Name, int Age) и добавлять в тело свойства public string Name { get; init; }, думая, что это две разные переменные. Нет! Это будет конфликт (повторное объявление). Либо используйте полностью позиционный синтаксис, либо явно определяйте свойства — не путайте.

Практические примеры

Мы продолжаем разрабатывать консольное приложение, в котором пользователь создает заказы. Пусть есть класс заказа Order, который до сих пор выглядел так:

public record Order(string Product, int Quantity, double Price);

Допустим, теперь нам нужна валидация количества (quantity не может быть меньше 1) и дополнительное свойство-счетчик общей стоимости:


public record Order
{
    public string Product { get; init; }
    public int Quantity { get; init; }
    public double Price { get; init; }
    public double TotalCost => Quantity * Price;

    public Order(string product, int quantity, double price)
    {
        Product = product ?? throw new ArgumentNullException(nameof(product));
        if (quantity < 1)
            throw new ArgumentException("Количество должно быть не меньше 1!");
        Quantity = quantity;
        Price = price;
    }

    public override string ToString()
        => $"Товар: {Product}, Кол-во: {Quantity}, Итог: {TotalCost}";
}

Заметьте, что мы реализовали явно свойства, сделали их с init-сеттером (неизменяемые объекты), добавили автоматическое вычисление стоимости — код стал гибче!

Вызов в программе:

var order = new Order("Велосипед", 2, 15000);
Console.WriteLine(order); // Товар: Велосипед, Кол-во: 2, Итог: 30000

3. record struct

Эволюция struct-ов

До появления record struct структуры в C# были "простыми рабочими лошадками" — быстро копируемые, хранящиеся в стеке, отлично подходящие для коротких “Parcel-ов” данных (например, координат или цветов). Но они не поддерживали всех плюшек records: не было позиционного синтаксиса, with-выражений, сравнения по значению по умолчанию и прочих “сладких булочек”.

Теперь в C# можно объявлять структуры в стиле record:

public record struct Point(int X, int Y);
Позиционный синтаксис record struct

А что это вообще даёт?

  • Автоматическая реализация Equals, GetHashCode, ToString — теперь ваша структура умеет красиво сравниваться и печататься!
  • Синтаксис with-клонирования: var p2 = p1 with { X = 10 };
  • Возможность использования позиционного или явного синтаксиса.

Сравнение: классический struct VS record struct

struct record struct
Позиционный синтаксис Нет Да
with-выражение Нет Да
Неизменяемость Нет (по умолч.) Да (init)
Сравнение по значениям Нет (по умолч.) Да
ToString Стандартный Красивый

Явный синтаксис тела для record struct

Всё так же, как у обычного record, только struct:


public record struct Rectangle
{
    public int Width { get; init; }
    public int Height { get; init; }

    public int Area => Width * Height;

    public Rectangle(int width, int height)
    {
        Width = width > 0 ? width : throw new ArgumentException("Ширина > 0");
        Height = height > 0 ? height : throw new ArgumentException("Высота > 0");
    }

    public void Print()
    {
        Console.WriteLine($"Размеры: {Width} x {Height}, площадь: {Area}");
    }
}

Пример использования:

var rect = new Rectangle(10, 7);
rect.Print(); // Размеры: 10 x 7, площадь: 70

// Клонируем и изменяем ширину, не меняя исходник
var wideRect = rect with { Width = 20 };
wideRect.Print(); // Размеры: 20 x 7, площадь: 140

Особенности record struct

  • Это всё ещё struct — value type. Копируется при присваивании!
  • Все плюсы records: сравнение по значениям, with-клонирование, красивый ToString.
  • Рекомендуется использовать для маленьких, компактных неизменяемых наборов данных, где важно избежать аллокаций в куче.
  • Можно объявлять позиционные параметры или явно “раскрывать” тело.

4. Типичные ошибки и грабли

Излишняя мутабельность: record struct не делает поля автоматически неизменяемыми, если вы объявляете их как обычные (например, public int Value;). Используйте init-сеттеры для truly immutable struct!

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

Копирование: Это struct, а значит... всё копируется! Не путайте с ссылочными record-айдами.

Путаница с with-выражениями: Они всегда создают shallow copy, то есть НЕ делают глубокое копирование вложенных объектов.

2
Задача
C# SELF, 19 уровень, 3 лекция
Недоступна
Создание record с явным телом
Создание record с явным телом
2
Задача
C# SELF, 19 уровень, 3 лекция
Недоступна
Работа с record struct
Работа с record struct
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ