JavaRush /Курси /C# SELF /Віртуальні методи в C#: поліморфізм і перевизначення

Віртуальні методи в C#: поліморфізм і перевизначення

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

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 буде викликано метод базового класу («Якийсь звук»), а не собаки. Несподівана зустріч із типами!

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