OOP Course
Сьогодні

Підрозділ 4.5

Відмінність перевизначення та приховування методів

Пояснює різницю між перевизначенням і приховуванням методів, роботу VMT і вибір реалізації під час виконання.

4.5. Відмінність перевизначення та приховування методів

У попередніх розділах ми розглянули два способи змінити поведінку методу в похідному класі: перевизначення (virtual + override) і приховування (new). Зовні вони схожі — в обох випадках похідний клас має метод з тим самим іменем, що й базовий. Але поводяться вони принципово по-різному, і розуміння цієї різниці є критичним для написання коректного поліморфного коду.

Перевизначення: пізнє зв'язування

При перевизначенні через virtual + override рішення про те, яку реалізацію методу викликати, приймається під час виконання програми на основі реального типу об'єкта. Цей механізм називається пізнім зв'язуванням (late binding або dynamic dispatch).

Змінна p оголошена як Person, але реальний об'єкт у пам'яті — Doctor. При виклику p.Print() середовище виконання перевіряє реальний тип об'єкта і знаходить реалізацію Doctor.Print().

Таблиця віртуальних методів (VMT)

Для реалізації пізнього зв'язування середовище виконання (CLR) під час завантаження типу формує для кожного класу таблицю віртуальних методів (Virtual Method Table, VMT). У цій таблиці зберігаються адреси реалізацій усіх віртуальних методів класу. Коли створюється об'єкт, у його заголовку зберігається посилання на опис свого реального класу, а через нього — на VMT. Компілятор C# лише генерує інструкцію callvirt і вказує, який віртуальний метод викликати; сама таблиця будується вже під час виконання.

При виклику p.Print():

  1. Середовище виконання бере посилання на VMT з об'єкта — це VMT класу Doctor
  2. У VMT Doctor знаходить адресу Doctor.Print()
  3. Виконує Doctor.Print()

Саме тому тип змінної не має значення — вирішує VMT об'єкта. Варто врахувати, що через необхідність звернення до VMT виклик віртуального методу є трохи повільнішим порівняно зі звичайним. Проте у переважній більшості задач ця різниця непомітна.

Приховування: раннє зв'язування

При приховуванні через new рішення приймається під час компіляції на основі типу змінної. Це раннє зв'язування (early binding або static dispatch). Похідний клас не замінює метод у VMT — він просто визначає новий метод, який існує паралельно:

Той самий об'єкт Doctor — але залежно від типу змінної викликається різна реалізація. Через Person p — Person.Print(), через Doctor d — Doctor.Print(). Компілятор прив'язує виклик до методу ще при компіляції, дивлячись лише на тип змінної.

Порівняння на одному прикладі

Щоб побачити різницю повністю, розглянемо масив Person[], де зберігаються різні об'єкти:

Якби замість override використовувалось new — всі три виклики у циклі виводили б Person.Print(), оскільки змінна циклу p має тип Person.

VMT та статична прив'язка — як вирішується виклик методуVMT та статична прив'язка — як вирішується виклик методу

Підсумок

override (перевизначення) new (приховування)
Зв'язування Пізнє (runtime) Раннє (compile time)
Визначає виклик Реальний тип об'єкта Тип змінної
Поведінка Поліморфна Залежить від змінної
Потребує virtual у базовому Будь-який метод
VMT Замінює запис Не змінює VMT

Загальне правило: якщо потрібна поліморфна поведінка — virtual + override. Якщо потрібно локально перевизначити невіртуальний метод без поліморфізму — new. Змішування цих підходів без чіткого розуміння є одним із найпоширеніших джерел прихованих помилок у C#.

Розроблено Tomka Yurii · © 2026 ·