1. Автоматичні властивості
У C# властивості (property) — це щось середнє між полем і методом. По суті, це синтаксичний цукор, який робить роботу з інкапсуляцією максимально зручною. Ви звертаєтеся до властивості так, ніби це звичайне поле, а всередині може бути будь-яка логіка: перевірка даних, зміна інших полів, виклик методів і навіть надсилання електронних листів вашій бабусі (останнє — не рекомендується). У минулій лекції ми писали властивості вручну. Але це швидко набридає, якщо у вас багато простих об’єктів, де потрібні лише «ґетер» і «сетер» — без додаткової логіки.
Знайомтеся — автоматичні властивості. Вони позбавлять вас шаблонного коду і дозволять C# дбати про зберігання значення без вашої участі.
Як було раніше: ручні властивості
Коли ми хочемо сховати поле, але дати до нього доступ через властивість, зазвичай пишемо ось так:
public class Dog
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
Такий код — типовий шаблонний (boilerplate). Часто в ці get/set нічого додавати не потрібно: достатньо надати доступ — і все.
Як можна тепер
У C# усе стало простіше: тепер не потрібно вручну прописувати поля — компілятор бере це на себе. Достатньо написати:
public class Dog
{
public string Name { get; set; }
}
Готово. Можна спокійно читати й змінювати значення Name, а за лаштунками компілятор сам створює приватне поле, до якого не дістатися напряму.
Що ж насправді відбувається за лаштунками? Коли в коді з’являється public string Name { get; set; }, компілятор автоматично створює приховане поле типу Name__BackingField. При кожному зверненні до Name він підставляє потрібний ґетер або сетер. Саме поле безпосередньо в коді не видно, але воно існує — і за допомогою рефлексії його навіть можна знайти (але це вже зовсім інша історія).
У чому перевага такого підходу? По-перше, це значно коротше і не потрібно повторювати одне й те саме. По-друге, код стає чистішим: менше візуального шуму, простіше читати й супроводжувати. І нарешті, це безпечніше — ніхто не зможе безпосередньо втручатися у поля: доступ здійснюється лише через властивості.
2. Синтаксис автоматичних властивостей
Синтаксис дуже простий:
public class Dog
{
public string Name { get; set; }
public int Age { get; set; }
}
Тепер наш Dog виглядає набагато лаконічніше! Ми можемо створювати об’єкти та задавати значення у звичний спосіб:
Dog myDog = new Dog();
myDog.Name = "Барбос";
myDog.Age = 4;
Схематично
| Спосіб | Синтаксис | Доступ до значення |
|---|---|---|
| Поле | |
Доступний безпосередньо |
| Ручна властивість | |
Доступ через get/set |
| Автоматична властивість | |
get/set + приховане поле |
3. Налаштування доступу: лише читання, лише запис
Лише для читання: get-only
Якщо властивість має бути лише для читання (наприклад, реєстраційний номер улюбленця), можна зробити її доступною тільки для читання — вказавши get, а set або взагалі не писати, або зробити приватним. Значення можна присвоїти лише в конструкторі чи безпосередньо під час оголошення:
public class Dog
{
public string RegistrationCode { get; }
public Dog()
{
RegistrationCode = "DOG-001"; // Присвоюємо значення в конструкторі
}
}
Тепер ззовні можна лише прочитати RegistrationCode, але не змінити. Це зручний спосіб захистити важливі дані від випадкових змін.
Ще варіант: приватний set
public string Name { get; private set; }
Тоді властивість можна змінити лише всередині класу (наприклад, через метод або конструктор).
Лише для запису: сумнівна ідея
Теоретично можна зробити set-only властивість (тільки для запису), але це трапляється дуже рідко й не рекомендується — уявіть об’єкт, який поводиться як «чорна діра»: туди можна щось покласти, а дістати назад — ніколи.
4. init-only сетери
Коротка передісторія
Раніше, щоб створити незмінний об’єкт (immutable), зазвичай оголошували лише властивості з get і задавали значення через конструктор. Це зручно, але не завжди. Нещодавно в C# з’явився новий синтаксис: init-only властивості.
У чому суть init-сетера?
- Дозволяє встановити значення властивості лише під час створення об’єкта (тобто у конструкторі або в ініціалізаторі об’єкта).
- Після створення об’єкта властивість стає лише для читання.
- Ідеально підходить для «майже незмінних» класів, DTO, конфігурацій і моделей.
public class Dog
{
public string Name { get; init; }
public int Age { get; init; }
}
Тепер ми можемо зробити так:
Dog dog = new Dog { Name = "Бобик", Age = 2 };
dog.Name = "Рекс"; // Помилка! Після створення - тільки читання
Значення також можна задавати в конструкторі:
public Dog(string name, int age)
{
Name = name;
Age = age;
}
Схема: коли можна присвоювати
| Де присвоювати? | { get; set; } | { get; private set; } | { get; init; } |
|---|---|---|---|
| Ззовні класу, після створення | ✅ | ❌ | ❌ |
| У конструкторі | ✅ | ✅ | ✅ |
| В ініціалізаторі | ✅ | ❌ | ✅ |
| Усередині класу | ✅ | ✅ | ❌ (крім конструктора) |
5. Застосування на практиці
Давайте перепишемо наш клас Dog, використавши автоматичні та init-only властивості:
public class Dog
{
// Властивість: встановлюється лише під час ініціалізації
public string Name { get; init; }
public int Age { get; init; }
// Автоматична властивість для поточного стану
public bool IsHungry { get; set; }
// Метод: собака гавкає
public void Bark()
{
Console.WriteLine($"{Name} каже: Гав!");
}
}
Тепер можна створювати Dog ось так:
Dog dog = new Dog { Name = "Шарик", Age = 3, IsHungry = true };
dog.Bark(); // виведе: Шарик каже: Гав!
dog.IsHungry = false; // цю властивість можна змінювати після створення
dog.Name = "Барбос"; // Помилка! Властивість тільки для ініціалізації
6. Автоматичні властивості з початковими значеннями
У C# усе зроблено для вашої зручності. Можна навіть одразу задати початкове значення властивості:
public class Dog
{
public string Name { get; set; } = "Безіменний";
public int Age { get; set; } = 0;
}
Або з init:
public class Dog
{
public string Name { get; init; } = "Щеня";
public int Age { get; init; } = 0;
}
Тепер, якщо не вказати Name під час створення, воно матиме значення «Щеня».
7. Автоматичні властивості з різним рівнем доступу
Можна зробити так, щоб властивість читалася й записувалася з різними модифікаторами доступу:
public class Dog
{
public string Name { get; private set; }
public int Age { get; private set; }
public Dog(string name, int age)
{
Name = name;
Age = age;
}
}
У цьому випадку змінити імʼя й вік можна лише всередині класу Dog, наприклад, у методах або конструкторі.
Порівняння ручних і автоматичних властивостей
| Підхід | Складність написання | Контроль у set/get | Змінюваність після створення | Використання в моделях |
|---|---|---|---|---|
| Ручна властивість | Довго | Повний | Будь-яка | Там, де потрібна логіка |
| Автоматична властивість | Коротко | Немає | Будь-яка або приватна | Майже всюди |
| Автоматична з init-only | Коротко | Немає | Тільки при створенні | DTO, конфігурації |
8. Типові помилки при роботі з властивостями
Помилка № 1: спроба присвоїти значення get-only властивості поза конструктором.
Якщо властивість оголошена лише з get, компілятор не дозволить змінити її значення десь ще. Присвоювати можна лише в конструкторі або прямо під час оголошення. Забули — отримаєте помилку компіляції.
Помилка № 2: плутанина між полем і властивістю.
Коли замінюють звичайне поле на автоматичну властивість, важливо переконатися, що в іншому коді немає прямого звернення до поля. Поле і властивість — різні сутності, і якщо залишити старі звернення, компілятор або видасть помилку, або програма поводитиметься непередбачувано.
Помилка № 3: приватний set без розуміння наслідків.
Якщо у властивості є приватний set, а в іншому коді все ще намагаються присвоювати йому значення — буде помилка. Часто це трапляється випадково, особливо під час копіювання чужого коду. Завжди перевіряйте, чи доступний set там, де ви маєте намір його використовувати.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ