Підрозділ 3.2
Конструктори, ініціалізатори та деконструктори
Пояснює конструктори, перевантаження, this, ланцюжки конструкторів, ініціалізатори об'єктів і деконструктори.
3.2. Конструктори, ініціалізатори та деконструктори
Створення конструкторів
У попередньому розділі об'єкти створювались через конструктор за замовчуванням, який C# генерує автоматично. Проте у реальній розробці майже завжди виникає потреба визначити власний конструктор — щоб об'єкт одразу отримував коректні початкові значення і не міг існувати в «порожньому» стані. Уявіть картку пацієнта у клінічній системі: безглуздо мати об'єкт Patient, у якого ні імені, ні віку — такий стан є семантично некоректним. Конструктор вирішує цю проблему, гарантуючи, що кожен новий об'єкт одразу містить усі необхідні дані.
На рівні коду конструктор — це метод, який має те саме ім'я, що й клас, може приймати параметри, але не має типу, що повертається (навіть void). Варто пам'ятати: щойно ви визначаєте в класі хоча б один власний конструктор, компілятор більше не генерує конструктор за замовчуванням автоматично.
Визначимо у класі Patient найпростіший конструктор без параметрів, який встановлює початкові значення:
Конструктори можуть мати модифікатори доступу. У прикладі вище конструктор оголошено з public — це дозволяє створювати об'єкти Patient з будь-якого місця програми. Якби конструктор був private, об'єкт можна було б створити лише всередині самого класу (такий прийом використовується, наприклад, у патерні Singleton).
Перевантаження конструкторів
Клас може мати кілька конструкторів — це називається перевантаженням (overloading). Компілятор розрізняє їх за кількістю та типами параметрів. Це зручно: залежно від того, яка інформація відома в момент реєстрації пацієнта, можна викликати відповідний варіант конструктора.
У прикладі визначено три конструктори — кожен приймає різну кількість параметрів. Коли ми пишемо new Patient("Іван Петренко", 45), компілятор автоматично обирає третій варіант за сигнатурою.
Починаючи з C# 9, назву типу при виклику конструктора можна опустити, якщо тип відомий з контексту:
Patient p1 = new(); // аналогічно new Patient()
Patient p2 = new("Олена"); // аналогічно new Patient("Олена")
Patient p3 = new("Іван", 45); // аналогічно new Patient("Іван", 45)Це скорочення особливо зручне при ініціалізації полів або колекцій, де тип вже вказано ліворуч.
Ключове слово `this`
Ключове слово this — це посилання на поточний екземпляр класу. Найчастіше воно використовується тоді, коли параметри конструктора (або методу) мають ті самі імена, що й поля класу. Без this компілятор не може однозначно зрозуміти, де поле, а де параметр.
У виразі this.name = name ліва частина (this.name) означає поле поточного об'єкта, а права (name) — параметр конструктора. Якщо б параметри мали інші назви (наприклад, n та a), ключове слово this для доступу до поля не було б обов'язковим — хоча багато стилів кодування рекомендують використовувати this явно для ясності. Крім того, через this можна звертатися до будь-якого методу або поля класу з будь-якого іншого методу.
Ланцюжок виклику конструкторів
Коли в класі є кілька конструкторів, вони нерідко дублюють одну й ту саму логіку ініціалізації. Щоб уникнути дублювання, один конструктор може викликати інший за допомогою синтаксису : this(...). Це називається ланцюжком виклику конструкторів.
Важливо розуміти порядок виконання: спочатку виконується викликаний конструктор (той, що вказаний після :), і лише потім — тіло поточного. Тобто перший конструктор викликає другий, другий — третій, і лише після виконання третього управління повертається назад по ланцюжку.

Ланцюжок конструкторів особливо корисний, коли «повний» конструктор містить нетривіальну логіку валідації або ініціалізації — тоді всі спрощені конструктори просто делегують йому роботу, і ця логіка не повторюється.
Параметри за замовчуванням як альтернатива
Коли конструктори лише передають значення одне одному без додаткової логіки, увесь ланцюжок можна замінити одним конструктором з параметрами за замовчуванням. Це значно скорочує код і спрощує його розуміння:
Якщо при виклику конструктора аргумент для параметра не передається, автоматично підставляється значення за замовчуванням. Тобто new Patient() — це те саме, що new Patient("Невідомий", 0). Слід пам'ятати: параметри зі значеннями за замовчуванням завжди мають іти після параметрів без них.
Ініціалізатори об'єктів
Ініціалізатори — це синтаксична конструкція, яка дозволяє встановити значення доступних полів і властивостей об'єкта безпосередньо в момент його створення, без написання спеціального конструктора. Значення передаються у фігурних дужках після виклику конструктора:
При використанні ініціалізаторів слід враховувати два важливих моменти:
- За допомогою ініціалізатора можна встановити значення лише тих полів і властивостей, які доступні за межами класу (тобто мають модифікатор
public). Закриті (private) поля залишаються недоступними. - Ініціалізатор виконується після конструктора. Тому якщо і конструктор, і ініціалізатор встановлюють значення одного й того самого поля, перемагає значення з ініціалізатора.
Ініціалізатори особливо зручні, коли поле класу само по собі є об'єктом іншого класу. У такому випадку можна ініціалізувати вкладений об'єкт у тому ж виразі:
Зверніть увагу на синтаксис ініціалізації вкладеного об'єкта:
diagnosis = { title = "Грип" }Тут не створюється новий об'єкт Diagnosis — натомість змінюється поле title вже існуючого об'єкта, який конструктор Patient() створив раніше. Саме тому такий запис можливий лише якщо конструктор уже ініціалізував поле diagnosis.
Деконструктори
Деконструктори (не плутати з деструкторами) дозволяють розкласти об'єкт на окремі складові — повернути кілька значень одночасно без використання кортежів або допоміжних структур. Деконструктор визначається як метод з іменем Deconstruct і параметрами out:
Значення з деконструктора передаються змінним за позицією: перше out-значення — першій змінній, друге — другій. Насправді деконструктор — це синтаксичний цукор над явним викликом методу:
string name;
int age;
patient.Deconstruct(out name, out age);Обидва записи еквівалентні — компілятор перетворює деструктурування у звичайний виклик методу.
Буває, що не всі значення деконструктора потрібні. Щоб пропустити певне значення, замість змінної використовується символ відкидання _ (discard):
Оскільки перше значення — ім'я пацієнта, яке нас не цікавить, замість змінної стоїть _. Компілятор розуміє цей знак як «це значення мені не потрібне» і не виділяє для нього змінну.
Статичний конструктор
Окрім звичайних конструкторів, C# підтримує статичний конструктор — він виконується один раз автоматично перед першим зверненням до класу. Він не має модифікатора доступу, не приймає параметрів і не може бути викликаний явно. Його призначення — ініціалізація статичних полів або виконання одноразових налаштувань при «пробудженні» класу.
Зверніть увагу: статичний конструктор виконується до першого new Patient(...), але після рядка "Програма запускається..." — рівно в момент першого звернення до класу. Порядок гарантований середовищем виконання .NET.
Приватний конструктор
Конструктор може мати модифікатор private — тоді створити об'єкт класу ззовні буде неможливо. Це архітектурне рішення, яке використовується в патерні Singleton — коли система повинна мати рівно один екземпляр певного класу. У клінічній системі реєстр клініки логічно існує в єдиному примірнику:
Оскільки конструктор private, єдиний спосіб отримати об'єкт — через GetInstance(). Обидві змінні r1 і r2 вказують на один об'єкт — ReferenceEquals повертає true.
Первинні конструктори (C# 12)
Починаючи з C# 12, параметри конструктора можна оголошувати прямо в заголовку класу — це називається первинним конструктором (primary constructor). Він скорочує шаблонний код і робить оголошення класу компактнішим:
Параметри первинного конструктора (name, age) доступні у всьому тілі класу. Якщо потрібні додаткові конструктори — вони делегують виклик первинному через this(...):
Первинні конструктори особливо зручні для простих класів-моделей, де потрібно лише зберегти кілька значень і надати до них доступ.