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, компилятор не считает это переопределением. Это называется скрытие метода (будет рассмотрено отдельно). В таком случае вызов метода через переменную базового типа приведёт к неожиданному результату — будет вызван базовый метод, а не ваш.

2
Задача
C# SELF, 20 уровень, 3 лекция
Недоступна
Полиморфизм с переопределением методов
Полиморфизм с переопределением методов
2
Задача
C# SELF, 20 уровень, 3 лекция
Недоступна
Использование базовой реализации через `base`
Использование базовой реализации через `base`
Комментарии (3)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Slevin Уровень 59
3 февраля 2026
Почти полное повторение прошлой информации, два новых слова "sealed" и "new".
whynot00 Уровень 1
24 сентября 2025
ощущение что все темы на этом уровне вообще не связаны друг с другом
Александр Уровень 40
11 января 2026
да не, пока всё норм, кроме того, что идёт повторение части инфы