JavaRush /Курси /C# SELF /record з явним тілом ...

record з явним тілом і record struct

C# SELF
Рівень 19 , Лекція 3
Відкрита

1. Вступ

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

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

Але іноді у вас з’являється сильне бажання додати до record ще якісь методи, нетипові властивості, змінити модифікатори доступу, додати логіку до конструктора (наприклад, валідацію або «автоматичне» перетворення даних). На жаль, у позиційному записі цьому просто ніде бути! Час переходити до явного синтаксису тіла, якщо:

  • Вам потрібно розширити record методами, властивостями або додатковою логікою.
  • Потрібно контролювати поведінку властивостей (сетери, ґетери, init, валідація).
  • Потрібно створити кілька різних конструкторів для різних способів ініціалізації.
  • Треба додати інтерфейси або реалізувати спеціальні методи.

Будуємо 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# були «простими робочими конячками» — швидко копіювалися, зберігалися у стеку, чудово підходили для коротких наборів даних (наприклад, координат або кольорів). Але вони не підтримували всіх можливостей 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 для по-справжньому незмінного struct!

Порівняння: якщо ви додаєте нові поля вручну (не включаючи їх у позиційний синтаксис), майте на увазі: лише ті поля, які в позиційному конструкторі або з сетером init, беруть участь в автоматичному порівнянні за значенням.

Копіювання: це struct, а отже… усе копіюється! Не плутайте з посилальними record-ами.

Плутанина з with-виразами: вони завжди створюють поверхневу копію (shallow copy), тобто не виконують глибокого копіювання вкладених об’єктів.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ