JavaRush /Курси /C# SELF /Перевизначення методів (Method Overriding) у C#

Перевизначення методів (Method Overriding) у C#

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

1. Вступ

Якщо вам стало трохи не по собі від слова перевизначення, не переймайтеся — це не страшно й навіть зручно! Перевизначення методу — це можливість підмінити поведінку методу базового класу власною у похідному класі. Завдяки цьому наш код стає гнучким, розширюваним і готовим до реального життя, де кожна тварина точно не хоче бути просто «якимось звуком».

Щоб дозволити перевизначити метод, у базовому класі метод позначають ключовим словом virtual. У похідному класі, щоб замінити реалізацію, використовують ключове слово override.

Приклад


public class Animal
{
    public string Name { get; set; }
    public virtual void MakeSound()
    {
        Console.WriteLine("Деякий універсальний звук тварини...");
    }
}
        

А тепер — у класі собаки:


public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Гав-гав!");
    }
}
        
  • У базовому класі — virtual.
  • У похідному класі — override.
  • Сигнатура методу (імʼя, тип поверненого значення, параметри) має збігатися.

Візуальна схема

Клас Dog наслідує Animal і може перевизначити MakeSound()

2. Демонстрація роботи

Давайте подивимося, як працює перевизначення на практиці. Створимо тварин і перевіримо звук:

Animal pet1 = new Animal { Name = "Безіменна тварина" };
Dog pet2 = new Dog { Name = "Барбос" };

pet1.MakeSound(); // Виведе: Деякий універсальний звук тварини...
pet2.MakeSound(); // Виведе: Гав-гав!

Тепер ускладнимо завдання:
Що, якщо ми збережемо собаку у змінну типу Animal?

Animal pet3 = new Dog { Name = "Шарик" };
pet3.MakeSound(); // ???

Як вважаєте, що станеться?
Відповідь: Виведеться «Гав-гав!»
Адже навіть якщо змінна має тип Animal, вона «посилається» на собаку, тож буде викликано перевизначену версію методу!
Ось вона — магія динамічного (або пізнього) зв’язування.

3. Використання ключового слова base при перевизначенні

Іноді потрібно не повністю замінити реалізацію методу, а розширити її — наприклад, додати щось своє, а потім виконати стару поведінку. Для цього використовують ключове слово base. Воно дозволяє викликати версію методу з базового класу.


public class Cat : Animal
{
    public override void MakeSound()
    {
        base.MakeSound(); // виклик базової реалізації
        Console.WriteLine("Мяу!");
    }
}
        

Під час виклику цього методу спочатку буде виведено «Деякий універсальний звук тварини…», а потім «Мяу!».

4. Як працює вибір методу при перевизначенні

Щоб наочно зрозуміти, що відбувається «під капотом», уявіть собі таку таблицю (віртуальна диспетчеризація):

Тип змінної Тип об’єкта Який метод буде викликано
Animal Animal Animal.MakeSound
Animal Dog Dog.MakeSound
Animal Labrador Labrador.MakeSound
Dog Labrador Labrador.MakeSound
Dog Dog Dog.MakeSound

Головне правило:
Тип змінної важливий лише для компілятора, а під час виконання враховується тип фактичного об’єкта (те, що ми створили через new).
Цей механізм називається динамічним (або пізнім) зв’язуванням — саме на ньому ґрунтується поліморфізм (про це — у наступній лекції!).

Навіщо перевизначати методи

  • У GUI-фреймворках: у вас є базовий клас вікна, і ви перевизначаєте методи для малювання конкретних елементів.
  • У ігрових рушіях: є базовий клас Enemy, а похідні класи реалізують різні типи поведінки.
  • У юніт-тестах: можна створювати «заглушки» (stubs, mocks) для методів.

Сучасні фреймворки .NET активно використовують цей механізм для подій, шаблонного коду, наслідування конфігурацій і навіть для серіалізації об’єктів (наприклад, через віртуальні властивості).

5. Ключове слово new при приховуванні методів

Ми вже з’ясували, що для перевизначення методів потрібен дует virtual/override. Але в C# є ще один модифікатор, пов’язаний із методами в ієрархії наслідування — це new.

Навіщо потрібен new?

new використовують, якщо у похідному класі оголошено метод з тією ж сигнатурою, що й у базовому класі, АЛЕ ви не хочете перевизначати віртуальний метод, а саме приховати (замаскувати) базовий метод.

  • Це не перевизначення, а приховування.
  • Такий метод викликається за типом змінної, а не за фактичним типом об’єкта (немає динамічного поліморфізму!).
  • Компілятор попередить, якщо ви «випадково» приховали метод без ключового слова new.

Приклад: різниця між override і new


public class Animal
{
    public virtual  void MakeSound()
    {
        Console.WriteLine("Тварина видає якийсь звук...");
    }
}

public class Dog : Animal
{
    // Приховуємо метод базового класу (НЕ перевизначаємо)
    public new void MakeSound()
    {
        Console.WriteLine("Це не override! Просто собачий метод.");
    }
}
        

Тепер подивімося на поведінку:

Animal a = new Dog();
Dog d = new Dog();

a.MakeSound(); // "Тварина видає якийсь звук..."
d.MakeSound(); // "Це не override! Просто собачий метод."
  • Якщо змінна типу Dog — буде викликано метод із Dog.
  • Якщо змінна типу Animal, навіть якщо в ній збережено Dog — буде викликано метод Animal!

6. Зворотний зв’язок і особливості реалізації

На перших порах у програмуванні часто трапляється непорозуміння: метод «перевизначено», але чомусь він працює по-старому. Зазвичай причина проста: у базовому класі немає virtual, або в похідному метод оголошено з new, а не з override. Другий випадок особливо підступний — якщо викликати метод через змінну базового типу, буде викликано базову версію, а не перевизначену. Тож завжди стежте, щоб правильно використовувати ключові слова.

Окрім синтаксичних помилок, іноді початківці намагаються змінити тип, що повертається, під час перевизначення. Наприклад, зробити базову функцію з типом object, а в похідній — з типом string. Так робити не можна: сигнатура методу має повністю збігатися.

Таблиця порівняння: override vs new

Особливість override new
Механізм Перевизначає віртуальний метод Приховує метод базового класу
Пізнє зв’язування Так — працює через динамічний поліморфізм Ні — працює за типом змінної
Вимагає, щоб у базовому класі був… virtual, abstract або вже override Ні
Рекомендується використовувати Так, майже завжди Тільки у виняткових випадках

7. Робота перевизначених методів з ієрархіями

Уся ця історія з перевизначенням стає особливо цікавою, якщо в нас є довгі ланцюги наслідування:


public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Тварина щось робить...");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Гав-гав!");
    }
}

public class Labrador : Dog
{
    public override void MakeSound()
    {
        Console.WriteLine("Я лабрадор: вау-вау!");
    }
}
        

Що відбувається, якщо ми напишемо:

Animal pet = new Labrador();
pet.MakeSound(); //   => "Я лабрадор: вау-вау!"

C# завжди обирає найглибшу реалізацію віртуального методу, що є в самому об’єкті.

8. Типові помилки при перевизначенні методів

Світ неідеальний, і студенти (та досвідчені розробники) іноді помиляються. Давайте одразу навчимося уникати найпопулярніших «граблів»:

1. Забули virtual у базовому класі


public class Animal
{
    public void MakeSound() { ... } // Немає 'virtual'
}

public class Dog : Animal
{
    // Помилка компіляції! Не можемо перевизначити.
    public override void MakeSound()
    {
        Console.WriteLine("Гав!");
    }
}
        

C# одразу скаже: «Dog.MakeSound()»: no suitable method found to override

2. Сигнатури не збігаються

Переконайтеся, що імʼя методу, тип, що повертається, і параметри збігаються:


public class Animal
{
    public virtual void MakeSound() { ... }
}

public class Dog : Animal
{
    // Помилка: сигнатура відрізняється (наприклад, додано параметр)
    public override void MakeSound(string sound)
    {
        Console.WriteLine(sound);
    }
}
        

3. Не використовуйте new замість override без потреби

Ключове слово new дозволяє приховувати метод базового класу, але це не перевизначення і не працює через динамічний поліморфізм. Це інший механізм, якого зазвичай слід уникати без вагомої причини.

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