1. Вступ
Уявіть, у вас є базовий клас Animal з методом Speak(). Для всіх тварин за замовчуванням він виводить «Якийсь звук». Ви хочете створити похідні класи, наприклад, Dog і Cat, щоб вони говорили не «Якийсь звук», а свої справжні фрази («Гав!» і «Мяу!»).
Можна було б у кожному похідному класі написати власний метод Speak(), але якщо ви працюєте з колекцією типу Animal[], виклик animal.Speak() усе одно звернеться до методу Animal, а не собаки чи кота — адже C# за замовчуванням орієнтується на тип змінної, а не на реальний обʼєкт, на який вона посилається.
Ось приклад «на пальцях»:
Animal dog = new Dog();
dog.Speak(); // Що буде виведено?
За замовчуванням — «Якийсь звук», а не «Гав!», навіть якщо в класі Dog є свій метод Speak(). Що робити? Потрібно оголосити метод як virtual (у базовому класі) і override (у похідному класі).
Що таке віртуальний метод?
Віртуальний метод — це метод, оголошений у базовому класі з модифікатором virtual, який призначений для перевизначення у похідних класах ( override).Під час виклику такого методу через посилання на базовий клас буде викликано метод самого «реального» обʼєкта. Як у житті: якщо ви приручили тварину, і це кіт, то він скаже «Мяу!», а не «Якийсь звук».
Віртуальні методи — це основа поліморфізму в C#.
2. Оголошення віртуальних методів
Як оголосити віртуальний метод
У базовому класі оголошуємо метод із ключовим словом virtual:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Якийсь звук");
}
}
У похідному класі використовуємо ключове слово override:
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Гав!");
}
}
public class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("Мяу!");
}
}
Приклад: демонстрація поліморфізму
// Наприклад, у файлі Program.cs
// Базовий клас Animal з віртуальним методом Speak
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Якийсь звук");
}
}
// Клас Dog, перевизначає поведінку Speak
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Гав!");
}
}
// Клас Cat, теж перевизначає Speak
public class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("Мяу!");
}
}
public static class Program
{
public static void Main()
{
Animal[] animals = new Animal[]
{
new Animal(),
new Dog(),
new Cat()
};
foreach (Animal animal in animals)
{
animal.Speak(); // Виведе: "Якийсь звук", "Гав!", "Мяу!"
}
}
}
Бачите, яка «магія» у віртуального методу? Навіть якщо змінна має тип Animal, викликається версія методу конкретного типу.
3. Що відбувається «під капотом»: віртуальна таблиця
Коротко про механізм
Коли ви оголошуєте метод як віртуальний, компілятор додає його до спеціальної «віртуальної таблиці» методів для кожного типу (майже як меню в їдальні для кожної страви). Коли викликається такий метод, C# дивиться не на тип змінної, а на те, що реально лежить за посиланням, — і «підсовує» той метод, який відповідає реальному типу обʼєкта.
Можна сказати, що C# сам підставляє потрібний рецепт замість універсального, якщо у класу є власний варіант.
Аналогія: договір із можливістю «дописати пункт дрібним шрифтом»
Уявіть, що у вас є договір оренди (базовий клас), у якому записано, що орендар за замовчуванням платить фіксовану суму раз на місяць (метод PayRent()). Але ви — кмітливий власник — дозволяєте в додатках до договору зазначати особливі умови (віртуальний метод). Якщо якийсь орендар (похідний клас) скористається цим правом, він напише свій override, і тепер плата береться по-особливому.
4. Коли варто використовувати віртуальні методи?
- Якщо метод у базовому класі має типово працювати для більшості похідних класів, але деякі з них можуть вимагати власного варіанту поведінки.
- Коли ви будуєте ієрархію класів, у якій передбачається розширення або зміна частини логіки.
- Коли ви розробляєте гнучку архітектуру, наприклад, для бізнес-логіки, де різні типи операцій можуть потребувати індивідуальної поведінки.
У реальних проєктах віртуальні методи — основа патернів Шаблонний метод (Template Method), Стратегія (Strategy) і загалом значної частини ООП.
Порівняння зі звичайними методами
| Звичайний метод | Віртуальний метод | |
|---|---|---|
| Можна перевизначати | Ні | Так (override) |
| Як викликається | За типом змінної | За «справжнім» типом обʼєкта |
| Поліморфізм | Ні | Так |
Часті питання
- Чи можна зробити конструктор віртуальним?
Ні! Конструктори ніколи не бувають віртуальними — вони завжди працюють суто за типом обʼєкта. - Чи можуть віртуальні методи бути статичними?
Ні, тільки екземплярні методи можуть бути віртуальними. Статичні методи не беруть участі в поліморфізмі, адже немає обʼєкта — а хто ж тоді буде перевизначати? - Чи можуть поля бути віртуальними?
Ні, тільки методи, властивості та події.
5. Практика: розвиваємо застосунок «Тваринний світ»
У межах нашого навчального застосунку ви вже створили просту ієрархію тварин. Додаймо тваринам нові віртуальні методи, щоб різні тварини могли їсти по-різному!
Приклад коду з коментарями
// У базовому класі задаємо віртуальний метод Eat
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Якийсь звук");
}
public virtual void Eat()
{
Console.WriteLine("Їсть звичайну їжу.");
}
}
// У класі Dog перевизначаємо Eat
public class Dog : Animal
{
public override void Speak() => Console.WriteLine("Гав!");
public override void Eat() => Console.WriteLine("Їсть собачий корм.");
}
// У класі Cat також власна поведінка
public class Cat : Animal
{
public override void Speak() => Console.WriteLine("Мяу!");
public override void Eat() => Console.WriteLine("Їсть рибу.");
}
public static class Program
{
public static void Main()
{
Animal[] animals = new Animal[]
{
new Animal(),
new Dog(),
new Cat()
};
foreach (var animal in animals)
{
animal.Speak();
animal.Eat();
Console.WriteLine("---");
}
}
}
Тепер кожен кіт і пес харчується по-своєму! А базовий Animal і далі «їсть щось звичне» — як базова реалізація з типовою поведінкою.
6. Типові помилки та нюанси
- Можна забути override у похідному класі — і тоді метод базового класу так і залишиться активним.
- Або, навпаки, випадково не вказати virtual у базовому класі — тоді в похідному класі не можна буде написати override.
Не можна написати override без virtual
Компілятор C# не дозволить перевизначити метод, який у базовому класі не оголошено як віртуальний (або абстрактний).
Навіщо потрібен override
override прямо повідомляє компілятору й читачеві коду: «Я не просто пишу новий метод — я свідомо змінюю те, що задано у базовому класі». Це запобігає помилкам, коли ви хочете «перевизначити», але випадково просто пишете новий метод із подібною сигнатурою.
Якщо забули override і написали новий метод
public class Dog : Animal
{
public void Speak()
{
Console.WriteLine("Гав!");
}
}
Але це не перевизначення! Під час звернення до Dog через змінну типу Animal буде викликано метод базового класу («Якийсь звук»), а не собаки. Несподівана зустріч із типами!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ