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
Механизм Переопределяет виртуальный метод Скрывает метод базового класса
Позднее связывание Да — работает через динамический полиморфизм Нет — работает по типу переменной
Требует, чтобы в base был... 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 позволяет скрывать метод базового класса, но это не переопределение и не работает через динамический полиморфизм. Это другой механизм, который обычно следует избегать без веской причины.

2
Задача
C# SELF, 21 уровень, 2 лекция
Недоступна
Использование базового метода при переопределении
Использование базового метода при переопределении
2
Задача
C# SELF, 21 уровень, 2 лекция
Недоступна
Работа с цепочкой наследования
Работа с цепочкой наследования
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Александр Уровень 24
13 января 2026
повторение мать учения? уже третий раз одно и тоже