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

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

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

1. Вступ

Уявімо, що у нас є застосунок для обліку тварин у зоопарку (той самий приклад, який ми почали розвивати на попередніх лекціях). Ми створили базовий клас Animal з методом MakeSound, щоб усі тварини могли «видавати» якийсь звук.


public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Тварина видає звук.");
    }
}
        
Базовий клас із віртуальним методом

Та раптом зʼясовується, що у лева та папуги звуки зовсім різні. Універсальне «Тварина видає звук» уже не підходить. Саме тут у пригоді стає перевизначення методів. Вам потрібно, щоб кожен похідний клас звучав по‑своєму!

2. Синтаксис: як працює override

Основні правила

  • Щоб перевизначити метод, він має бути позначений у базовому класі як virtual (або abstract).
  • У похідному класі використовуємо ключове слово override перед методом із такою самою сигнатурою.

Приклад: леви та папуги

public class Lion : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Ррррр!");
    }
}

public class Parrot : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Папуга дурень!");
    }
}

Тепер, якщо ви створите список тварин і викличете для кожного MakeSound, кожен «говоритиме» по‑своєму:

Animal[] zoo = new Animal[]
{
    new Lion(),
    new Parrot(),
    new Animal()
};

foreach (var animal in zoo)
{
    animal.MakeSound();
}
// Виведення:
// Ррррр!
// Папуга дурень!
// Тварина видає звук.
Важливо: C# викличе саме ту реалізацію методу, яка відповідає реальному ( run-time) типу об’єкта, навіть якщо змінну оголошено типом Animal!

3. Віртуальна таблиця викликів (v-table)

Коли ви використовуєте virtual і override, компілятор генерує для класу спеціальну «віртуальну таблицю методів» (v-table).
При виклику методу через посилання на базовий клас CLR звертається до таблиці: чи не перевизначено цей метод у похідному класі? Якщо так — викликається варіант із похідного класу.
Це й є «магія» пізнього зв’язування (late binding), або динамічного поліморфізму.

4. З’єднуємо разом: продовжуємо розвивати наш застосунок

Додаймо ще один клас:

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

Скористаймося цим у нашій мініпрограмі обліку тварин:

Animal[] zoo = new Animal[]
{
    new Lion(),
    new Parrot(),
    new Dog()
};

foreach (var animal in zoo)
{
    animal.MakeSound();
}

Тепер розширюваність і підтримуваність коду зросли у рази, а додавати нових тварин — одне задоволення!

5. Перевизначення властивостей і override з поверненим значенням

Методи — не єдине, що можна перевизначати. Ви також можете перевизначати віртуальні властивості та індексатори.

public class Animal
{
    public virtual string Name { get; set; } = "Тварина";
}

public class Lion : Animal
{
    public override string Name { get; set; } = "Лев";
}

Це особливо зручно, коли треба уточнити інформацію для конкретного виду.

6. Базова реалізація за допомогою base

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

public class Parrot : Animal
{
    public override void MakeSound()
    {
        base.MakeSound(); // "Тварина видає звук."
        Console.WriteLine("Папуга дурень!");
    }
}

У цьому випадку папуга спочатку видасть загальний «зоопарковий» звук, а потім скаже своє фірмове «Папуга дурень!».

7. Практична користь: де це застосовують

Розуміння механізму override необхідне практично у будь-якому серйозному проєкті на C#, де активно використовують ООП.

  • Моделі тварин у нашому зоопарку? Вже робили.
  • Створення власних елементів керування для UI: Ви перевизначаєте стандартні візуальні методи, додаючи власну логіку.
  • Гнучка бізнес-логіка: Дозволяє будувати «скелет» поведінки в базових класах, а конкретику реалізовувати у похідних класах.
  • Тестування та моки: Легко «підміняти» методи через нащадків для юніт‑тестів.
  • Плагіни та розширення: Інтерфейс або абстрактний базовий клас, багато реалізацій — і все працює завдяки правильному перевизначенню.

8. sealed override і віртуальні методи у ланцюжках

Якщо ви не хочете, щоб хтось далі в ієрархії перевизначав ваш перевизначений метод, можна застосувати sealed override:

public class Base
{
    public virtual void Foo() { }
}

public class Middle : Base
{
    public sealed override void Foo() { }
}

public class Last : Middle
{
    public override void Foo() {} // Помилка — не можна перевизначити!
}

Це дозволяє «закрити» ланцюжок перевизначень там, де це критично для коректної роботи системи.

9. Новий метод (ключове слово new)

Буває, що потрібно оголосити у похідному класі метод із такою самою сигнатурою, але базовий не virtual.
У такому випадку можна використати ключове слово new — але це не поліморфізм, а радше «приховування»:

public class Animal
{
    public void MakeSound()
    {
        Console.WriteLine("Я тварина!");
    }
}

public class Cat : Animal
{
    public new void MakeSound()
    {
        Console.WriteLine("Я котик!");
    }
}

Animal animal = new Cat();
animal.MakeSound(); // "Я тварина!"
Cat cat = new Cat();
cat.MakeSound(); // "Я котик!"

Тут усе визначає тип змінної в момент виклику! Тому для справжнього динамічного поліморфізму завжди використовуйте комбінацію virtual + override.

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

Помилка № 1: спроба перевизначити метод, який не був оголошений як virtual, abstract або override.
У C# не можна перевизначати звичайні методи. Якщо метод у базовому класі не позначений спеціальним модифікатором (virtual, abstract або override), спроба написати override у похідному класі спричинить помилку компіляції.

Помилка № 2: невідповідність сигнатур методу.
Щоб метод був справді перевизначений, його імʼя, тип, що повертається, і параметри мають повністю збігатися з методом у базовому класі. Найменша розбіжність (наприклад, інший тип параметра) призведе до створення нового методу, а не до перевизначення.

Помилка № 3: забули модифікатор override.
Якщо ви визначили метод із такою самою сигнатурою, як у базовому класі, але не вказали override, компілятор не вважатиме це перевизначенням. Це називається приховуванням методу (буде розглянуто окремо). У такому випадку виклик методу через змінну базового типу призведе до неочікуваного результату — буде викликано базовий метод, а не ваш.

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