JavaRush /Курси /C# SELF /Властивості required ...

Властивості required та field

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

1. Вступ

Програмувати на C# без властивостей можна, та це приблизно як кататися на роликах у Білому домі — можна, але незручно. Ми вже звикли до лаконічного синтаксису без зайвих гетерів і сетерів. А коли наш застосунок починає працювати з «серйозними» моделями (наприклад, класом User, що описує користувача в системі), важливо гарантувати, що всі потрібні дані справді задані.

Наприклад, якщо у нас є клас:

public class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Легко забути ініціалізувати властивості:

User user = new User(); // Name буде null, Age = 0

У результаті десь за десять екранів і після сотні поворотів логіки ви отримаєте сумнозвісну NullReferenceException і довго шукатимете причину.

Нагадаємо про init-only властивості

Так, можна дозволити ініціалізацію лише під час створення:

public string Name { get; init; }

Але навіть із цим синтаксисом ніхто не змусить користувача класу передати значення, якщо він не захоче — конструктори за замовчуванням ініціалізують поля типовими значеннями (null, 0 тощо).

То як змусити розробника — або й себе — не забути задати потрібне значення? Саме для цього й придумали модифікатор required, який з’явився в C# 11.

2. required властивості: сувора ініціалізація

Модифікатор required — це вказівка компілятору стежити за тим, щоб конкретна властивість об’єкта була явно встановлена в момент його створення. Простіше кажучи, якщо таку властивість не ініціалізовано під час створення об’єкта — компілятор не пропустить, а IDE намалює страшну червону хвилю.


public class User
{
    public required string Name { get; set; }
    public int Age { get; set; }
}
Обовʼязкова властивість з required

Спробуймо створити користувача:

// Помилка компіляції: властивість Name є обов’язковою для ініціалізації!
User user1 = new User();

Або ось так:


// Помилка компіляції: не вказано обов’язкову властивість Name
User user2 = new User { Age = 18 };

А ось коректний спосіб:

User user3 = new User { Name = "Герміона", Age = 18 };

Як працює required?

Модифікатор required повідомляє компілятору: «Після завершення будь-якого конструктора об’єкта ця властивість має бути явно задана».

Це працює як для звичайних властивостей, так і для init-only:

public required string Name { get; init; }

Якщо ви визначаєте користувацький конструктор, який ініціалізує значення required-властивості, то правило теж дотримано.

Типова схема перевірки

Сценарій Компілятор задоволений?
Не задано required‑властивість
Задано в об’єктному ініціалізаторі ✔️
Задано в конструкторі ✔️

Приклад у нашій «собачій» моделі

public class Dog
{
    public required string Name { get; set; }
    public int Age { get; set; }
}

Dog dog = new Dog { Name = "Бобік", Age = 5 }; // Усе гаразд!
Dog badDog = new Dog { Age = 2 }; // Помилка! Не вказано Name.

Де реально допомагає required?

  • Передача DTO між шарами: якщо у вас є API, важливо, щоб на вхід завжди надходили всі потрібні поля.
  • Складні моделі з обов’язковими атрибутами: наприклад, Product з обов’язковим SKU, Order — з обов’язковим номером.
  • На співбесідах і під час ревʼю: якщо ви показуєте такий синтаксис, ваш код, найімовірніше, читатимуть із повагою (і легкою заздрістю).

3. Як required працює з конструкторами?

Іноді ви явно описуєте конструктор. Що буде, якщо required-властивість не ініціалізувати в конструкторі або об’єктному ініціалізаторі? Компілятор видасть помилку.


public class Article
{
    public required string Title { get; set; }
    public required string Author { get; set; }

    public Article()
    {
        // Якщо не ініціалізувати Title і Author — помилка компіляції!
        // Можна так:
        Title = "Без назви";
        Author = "Невідомий";
    }
}

Якщо конструктор сам присвоює значення required‑властивостям — усе чудово. Якщо ні, ви маєте ініціалізувати ці властивості через об’єктний ініціалізатор (new Article { ... }).

Специфіка використання

  • required працює лише з властивостями, а не з полями.
  • required не наслідується — якщо базова властивість позначена required, а в похідному класі ви не вказали required, компілятор не свариться (але гарною практикою буде явно повторити required у нащадку).
  • required не можна застосувати до автоматичних полів або до чогось іще, окрім властивостей.

4. Стрілочний запис властивостей

З появою нових версій C# розробники все частіше прагнуть до лаконічного та виразного коду. Одне з найпомітніших нововведень для властивостей — стрілочний запис (або expression-bodied properties).

Іноді потрібно визначити властивість, яка просто повертає значення без будь-якої додаткової логіки. Раніше для цього доводилося писати повний ґеттер із фігурними дужками:

public int Age
{
    get { return birthYear > 0 ? DateTime.Now.Year - birthYear : 0; }
}

Тепер можна писати набагато коротше — використовуючи стрілку (=>):


public int Age => birthYear > 0 ? DateTime.Now.Year - birthYear : 0;

Такий синтаксис називається властивістю з тілом-виразом (expression-bodied property). Він ідеально підходить для простих обчислень і робить код компактнішим.

Приклад використання get і set

public class Book
{
    private string _title;

    public string Title
    {
        get => _title;
        set => _title = value.Trim();
    }
}

Тут get повертає значення поля, а set — присвоює, попередньо прибираючи зайві пробіли на початку й у кінці.

Приклад використання тільки get (тільки для читання):

Якщо властивість тільки для читання, її можна записати взагалі без фігурних дужок, просто через =>.

public class Person
{
    private string name = "Марк Твен";

    // Тільки для читання: обчислювана властивість
    public string Name => name.ToUpper();
}

Тут властивість Name можна тільки прочитати — вона завжди повертає name у верхньому регістрі.

5. Ключове слово field для властивостей

До C# 14, якщо ви хотіли звернутися до прихованого поля автоматичної властивості прямо в сеттері або ґеттері (наприклад, щоб захиститися від рекурсії або додати власну логіку), — це було неможливо. Доводилося оголошувати поле явно.

C# 14 дозволяє звертатися до прихованого поля автоматичної властивості за допомогою ключового слова field:


public class Person
{
    public string Name
    {
        get => field; // field — це приховане поле властивості Name
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("Імʼя не може бути порожнім!");
            field = value; // використовуємо field замість явного _name
        }
    }
}

Раніше довелося б писати так:

private string _name;
public string Name
{
    get => _name;
    set
    {
        if (string.IsNullOrWhiteSpace(value))
            throw new ArgumentException("Імʼя не може бути порожнім!");
        _name = value;
    }
}

Стало на одне оголошення змінної менше. Чим код компактніший, тим краще.

Чому важливо іноді звертатися саме до поля всередині властивості?

  • Іноді потрібно явно контролювати де й як зберігається значення (наприклад, якщо ви хочете повертати копію об’єкта, кешувати результат або робити ледаче завантаження).
  • Якщо ви хочете використовувати атрибути або рефлексію — поле може знадобитися по імені.
  • У деяких випадках (де)серіалізації або оптимізації продуктивності вам потрібно більше контролю над зберіганням значення.

Варіанти використання:

Валідація даних у сеттері

public double Grade
{
    get => field;
    set
    {
        if (value < 0 || value > 5)
            throw new ArgumentOutOfRangeException("Оцінка має бути від 0 до 5");
        field = value;
    }
}

Статистика зміни значення

public int StepCount
{
    get => field;
    set
    {
        if (value > field)
        {
            Console.WriteLine($"Ура! Ви зробили {value - field} кроків більше!");
        }
        field = value;
    }
}

Ледаче завантаження

public string Data
{
    get
    {
        if (field == null)
            field = LoadDataFromDatabase();
        return field;
    }
    set => field = value;
}

6. Типові помилки та нюанси

Помилка № 1: забули ініціалізувати required-властивість.
Компілятор не пропустить такий код і видасть помилку одразу під час збирання, що допомагає уникнути проблем під час виконання програми.

Помилка № 2: required-властивості не працюють без повної ініціалізації в конструкторі.
Якщо конструктор не приймає значення для всіх обов’язкових властивостей, компілятор нагадає, що ви щось пропустили.

Помилка № 3: спроба використовувати required з const або readonly.
Ці модифікатори несумісні — required можна застосовувати лише до звичайних властивостей. Спроба їх поєднати призведе до помилки.

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