1. Вступ
Припустімо, у нас є клас Person:
public class Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
І ось ви вже майже геній — можна створювати людей з будь-яким імʼям і віком. Але трапляється, що інформації бракує. Користувач застосунку ввів лише імʼя, а вік зʼясується трохи пізніше. Або ви хочете інколи призначати «стандартний» вік за замовчуванням. Звісно, можна вигадати обхідні шляхи, але C# пропонує для такої задачі елегантне рішення — перевантаження конструкторів.
Перевантаження конструкторів означає, що в одному класі може бути кілька конструкторів з однаковим імʼям (назвою класу), але з різними списками параметрів.
Чим відрізняються ці списки? Вони можуть різнитися за:
- Кількістю параметрів: наприклад, один конструктор приймає 2 параметри, інший — 3.
- Типами параметрів: один приймає (string, int), інший — (string, decimal).
- Порядком параметрів: (string, int) і (int, string) — це різні сигнатури.
Найголовніше правило: компілятор розрізняє конструктори (і будь-які методи) за їхньою сигнатурою. Сигнатура включає імʼя конструктора (або методу) і список його параметрів (кількість, типи та порядок). Модифікатори доступу (public, private) і, для звичайних методів, тип, що повертається, не є частиною сигнатури. Конструктори не мають типу, що повертається, а їхнє імʼя завжди збігається з імʼям класу.
2. Перевантаження конструкторів: синтаксис і приклад
public class Cat
{
public string Name;
public string Color;
// Конструктор без параметрів
public Cat()
{
Name = "Безіменний кіт";
Color = "Сірий";
}
// Конструктор з одним параметром
public Cat(string name)
{
Name = name;
Color = "Сірий";
}
// Конструктор з двома параметрами
public Cat(string name, string color)
{
Name = name;
Color = color;
}
}
Використання:
Cat barsik = new Cat(); // "Безіменний кіт", "Сірий"
Cat murzik = new Cat("Мурзик"); // "Мурзик", "Сірий"
Cat ryzhik = new Cat("Рижик", "Рудий"); // "Рижик", "Рудий"
Якщо під час створення обʼєкта ви вказуєте параметри, C# автоматично визначить потрібний конструктор за їхньою кількістю та типом.
Це дуже зручно! Користувачу вашого класу не потрібно вказувати параметри, яких він не знає: якщо потрібен просто кіт — використовується конструктор без параметрів; якщо відоме імʼя — інший; якщо потрібен повний контроль над кольором — третій варіант.
3. Як працює виклик перевантажених конструкторів?
C# обирає потрібний конструктор автоматично на етапі компіляції, спираючись на тип і кількість переданих аргументів. Помилитися важко — якщо щось не так, компілятор одразу повідомить про помилку.
Спробуймо додати конструктор з цілим параметром:
public Cat(int age)
{
Name = "Безіменний кіт";
Color = "Сірий";
// Додатковий код для віку
}
Тепер ми можемо створити ще й «котика за віком»:
Cat oldCat = new Cat(5); // Під виклик потрапить конструктор Cat(int age)
| Виклик конструктора | Який викличеться? |
|---|---|
|
Без параметрів |
|
З одним рядковим параметром |
|
З двома рядковими параметрами |
|
З одним цілим параметром |
4. Внутрішні виклики конструкторів: ключове слово this
Буває, що різні конструктори виконують схожу (або однакову) роботу. Щоб не дублювати код, можна «викликати один конструктор з іншого». Для цього використовують ключове слово this.
public class Cat
{
public string Name;
public string Color;
public Cat() : this("Безіменний кіт")
{
// Цей конструктор викликає Cat(string name)
}
public Cat(string name) : this(name, "Сірий")
{
// Цей конструктор викликає Cat(string name, string color)
}
public Cat(string name, string color)
{
Name = name;
Color = color;
}
}
У результаті:
- new Cat() викликає Cat(string name), який викликає Cat(string name, string color).
- Усі «дороги ведуть до Рима»: уся ініціалізація зосереджена в одному «головному» конструкторі.
Такий підхід називається constructor chaining (ланцюжок виклику конструкторів).
5. Приклади з життя
Створімо клас герой (Hero) для гри, у якого є імʼя та рівень. Ось як ми його могли б написати:
З одним конструктором:
public class Hero
{
public string Name;
public int Level;
public Hero(string name, int level)
{
Name = name;
Level = level;
}
}
А тепер дозволимо створювати його по-різному!
Кілька конструкторів:
public class Hero
{
public string Name;
public int Level;
// Якщо нічого не вказано, нехай герой буде "Безіменний 1-го рівня"
public Hero() : this("Безіменний", 1)
{
}
// Якщо знаємо тільки ім'я, рівень за замовчуванням — 1
public Hero(string name) : this(name, 1)
{
}
// Найголовніший конструктор з двома параметрами
public Hero(string name, int level)
{
Name = name;
Level = level;
}
}
Тепер різними способами можна створити героя:
Hero h1 = new Hero(); // "Безіменний", 1
Hero h2 = new Hero("Артур"); // "Артур", 1
Hero h3 = new Hero("Лора", 10); // "Лора", 10
6. Деталі реалізації: підводні камені та нюанси
Перше: компілятор не створює «порожній» конструктор за замовчуванням, якщо ви оголосили хоча б один інший конструктор. Тому, якщо ви написали свій конструктор з параметрами, але забули додати конструктор без параметрів, то ось такий виклик:
Hero h = new Hero(); // Помилка, якщо немає конструктора без параметрів!
спричинить помилку компіляції.
Друге: якщо у ланцюжку виклику конструкторів викликати не той конструктор, можна порушити логіку ініціалізації. Важливо зберігати послідовність: краще, щоб уся реальна робота відбувалася в найповнішому конструкторі, а інші лише передавали туди дані через this(...).
Третє: якщо параметри відрізняються лише типами (наприклад, Cat(string s) і Cat(object o)), можна випадково отримати плутанину під час виклику конструктора з аргументом типу null. Компілятор не завжди зможе визначити, який саме конструктор ви хочете викликати.
7. Перевантаження та ініціалізація полів
Часто в класі є поля, які потрібно обовʼязково задати. Перевантаження конструкторів допомагає робити це зручно, але ви самі визначаєте, які значення за замовчуванням є розумними для вашого класу.
Приклад із перевіркою:
public class Book
{
public string Title;
public int Year;
// Книга за замовчуванням — "Без назви", 2000 рік
public Book() : this("Без назви", 2000)
{
}
public Book(string title) : this(title, 2000)
{
}
public Book(string title, int year)
{
Title = title;
Year = year;
}
}
8. Перевантаження з різними наборами параметрів
Трапляються класи, у яких кількість конструкторів обчислюється десятками. Це не завжди ознака гарної архітектури (інколи краще використовувати «налаштувальні» методи або шаблони «Builder»), але для користувацьких обʼєктів, моделей, DTO — цілком робочий варіант.
Приклад: клас «Улюбленець», у якому можна явно вказати всі характеристики, а можна — лише найнеобхідніше.
public class Pet
{
public string Name;
public int Age;
public string Type;
public bool IsVaccinated;
// Конструктор за замовчуванням
public Pet() : this("NoName", 0, "Cat", false) { }
// Мінімум інформації
public Pet(string name, string type) : this(name, 0, type, false) { }
// Повний конструктор
public Pet(string name, int age, string type, bool isVaccinated)
{
Name = name;
Age = age;
Type = type;
IsVaccinated = isVaccinated;
}
}
Різниця між перевантаженням методу й конструктора
| Звичайний метод | Конструктор (у тому числі, перевантажений) |
|---|---|
| Можна викликати будь-коли | Викликається лише під час створення обʼєкта (new) |
| Може повертати значення будь-якого типу | Ніколи не повертає значення (тип не вказується) |
| Імʼя методу може бути довільним, зазвичай із дієсловом | Імʼя завжди збігається з імʼям класу |
| Можна перевантажувати за параметрами | Конструктори також перевантажуються за параметрами |
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ