Почему необходимо иметь ключевое слово override перед абстрактными методами, когда мы реализуем их в дочернем классе?

9

Когда мы создаем класс, который наследуется от абстрактного класса, и когда мы реализуем унаследованный абстрактный класс, почему мы должны использовать ключевое слово override?

public abstract class Person
{
    public Person()
    {

    }

    protected virtual void Greet()
    {
        // code
    }

    protected abstract void SayHello();
}

public class Employee : Person
{
    protected override void SayHello() // Why is override keyword necessary here?
    {
        throw new NotImplementedException();
    }

    protected override void Greet()
    {
        base.Greet();
    }
}

Поскольку метод объявлен как абстрактный в его родительском классе, он не имеет никакой реализации в родительском классе, так почему здесь необходимо переопределение ключевого слова?

psj01
источник
2
Абстрактный метод неявно является виртуальным методом, по спецификации
Павел Аниховский
«Модификатор переопределения требуется для расширения или изменения абстрактной или виртуальной реализации унаследованного метода, свойства, индексатора или события». docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
gunr2171
2
Потому что, если вы не поместите его туда, вы «прячете» метод, а не переопределяете его. Вот почему вы получаете предупреждение, что «если это именно то, что вы хотели, используйте newключевое слово` ... Вы также получите ошибку« метод не переопределяет метод базового класса ».
Рон Бейер
@RonBeyer с виртуальным методом да, но с абстрактным он просто не скомпилируется.
Джонатан Барклай

Ответы:

14

Когда мы создаем класс, который наследуется от абстрактного класса, и когда мы реализуем унаследованный абстрактный класс, почему мы должны использовать ключевое слово override?

"Почему?" На подобные вопросы трудно ответить, потому что они расплывчаты. Я буду считать , что ваш вопрос «какие аргументы могут быть сделаны во время разработки языка , чтобы отстаивать позицию , что overrideэто ключевое слово требуется

Давайте начнем с шага назад. В некоторых языках, например, Java, методы являются виртуальными по умолчанию и переопределяются автоматически. Разработчики C # знали об этом и считали это незначительным недостатком Java. C # - это не «Java с удаленными глупыми частями», как говорили некоторые, но разработчики C # стремились извлечь уроки из проблемных аспектов проектирования C, C ++ и Java, а не копировать их в C #.

Разработчики C # считали переопределение возможным источником ошибок; в конце концов, это способ изменить поведение существующего, протестированного кода , и это опасно. Переопределение не является чем-то, что должно быть сделано случайно или случайно; это должно быть разработано кем-то, думающим об этом . Вот почему методы не являются виртуальными по умолчанию, и поэтому вы должны сказать, что вы переопределяете метод.

Это основные рассуждения. Теперь мы можем перейти к более сложным рассуждениям.

Ответ StriplingWarrior дает хороший первый аргумент в пользу более продвинутых аргументов. Автор производного класса может не знать о базовом классе, возможно, намеревается создать новый метод, и мы не должны позволять пользователю переопределять по ошибке .

Хотя это обоснованно, существует ряд контраргументов, таких как:

  • Автор производного класса обязан знать все о базовом классе! Они повторно используют этот код, и им следует тщательно изучить этот код, прежде чем его повторно использовать.
  • В вашем конкретном сценарии виртуальный метод является абстрактным; было бы ошибкой не игнорировать его, и поэтому маловероятно, что автор будет создавать реализацию случайно.

Давайте тогда сделаем еще более продвинутый аргумент по этому вопросу. При каких обстоятельствах автора производного класса можно извинить за то, что он не знает, что делает базовый класс? Хорошо, рассмотрим этот сценарий:

  • Автор базового класса составляет абстрактный базовый класс B.
  • Автор производного класса в другой команде создает производный класс D с помощью метода M.
  • Автор базового класса понимает, что командам, которые расширяют базовый класс B, всегда нужно будет предоставлять метод M, поэтому автор базового класса добавляет абстрактный метод M.
  • Что происходит в случае перекомпиляции класса D?

Мы хотим, чтобы автор D был проинформирован о том, что что-то важное изменилось . Что изменилось, так это то, что M теперь является требованием и что их реализация должна быть перегружена. DM может понадобиться изменить свое поведение, как только мы узнаем, что его можно вызывать из базового класса. Правильно не молча сказать: «О, ДМ существует и расширяет БМ». Правильная вещь для компилятора - сбой , и скажите «эй, автор D, проверь свое предположение, которое больше не действует, и исправь свой код, если необходимо».

В вашем примере, предположим , что overrideбыло необязательно на , SayHelloпотому что это переопределение абстрактный метод. Есть две возможности: (1) автор кода намеревается переопределить абстрактный метод, или (2) переопределяющий метод переопределяется случайно, потому что кто-то еще изменил базовый класс, и код теперь каким-то тонким образом неверен. Мы не можем отличить эти возможности, если overrideэто необязательно .

Но если overrideэто требуется , то мы можем сказать друг от друга три сценария. Если в коде overrideесть возможная ошибка, то она отсутствует . Если он намеренно переопределение , то overrideесть присутствует . И если оно намеренно не переопределено, то newоно присутствует . Дизайн C # позволяет нам сделать эти тонкие различия.

Помните, что сообщение об ошибке компилятора требует чтения мыслей разработчика ; компилятор должен вывести из неправильного кода, какой правильный код, скорее всего, имел в виду автор , и выдать ошибку, указывающую на правильное направление. Чем больше подсказок мы можем заставить разработчика оставить в коде о том, о чем они думали, тем лучше работа компилятора по составлению отчетов об ошибках и, следовательно, тем быстрее вы сможете найти и исправить свои ошибки.

Но в целом, C # был разработан для мира, в котором меняется код . На самом деле очень много возможностей C #, которые кажутся «странными», потому что они информируют разработчика, когда предположение, которое раньше было действительным , стало недействительным, потому что изменился базовый класс. Этот класс ошибок называется «хрупкие сбои базового класса», и в C # есть ряд интересных мер для этого класса сбоев.

Эрик Липперт
источник
4
Спасибо за разработку. Я всегда ценю ваши ответы как потому, что хорошо иметь авторитетный голос от кого-то «изнутри», так и потому, что вы отлично справляетесь с объяснением вещей просто и полностью.
StriplingWarrior
Спасибо за подробное объяснение! действительно ценю это.
psj01
Большие очки! Не могли бы вы перечислить некоторые другие смягчения, которые вы упомянули?
aksh1618
3

Он указывает, пытаетесь ли вы переопределить другой метод в родительском классе или создать новую реализацию, уникальную для этого уровня иерархии классов. Вполне возможно, что программист может не знать о существовании метода в родительском классе с точно такой же сигнатурой, что и у создаваемого им класса, что может привести к неприятным сюрпризам.

Хотя это правда, что абстрактный метод должен быть переопределен в неабстрактном дочернем классе, создатели C #, вероятно, чувствовали, что все же лучше четко указывать, что вы пытаетесь сделать.

StriplingWarrior
источник
Это хорошее начало для понимания рассуждений команды разработчиков языка; Я добавил ответ, который показывает, как команда начинает с идеи, которую вы изложили здесь, но продвигается дальше.
Эрик Липперт
1

Поскольку abstractметод является виртуальным методом без реализации, согласно спецификации языка C # , это означает, что абстрактный метод неявно является виртуальным методом. И overrideиспользуется для расширения или изменения абстрактной или виртуальной реализации, как вы можете видеть здесь

Чтобы перефразировать это немного - вы используете виртуальные методы для реализации некоторого позднего связывания, тогда как абстрактные методы вынуждают подклассы типа явно переопределять метод. В том-то и дело, что когда метод есть virtual, он может быть переопределен, когда он abstract- он должен быть переопределен

Павел Аниховский
источник
1
Я думаю, что вопрос не в том, что он делает, а в том, почему он должен быть явным.
Джонатан Барклай
0

Чтобы добавить ответ @ StriplingWarrior, я думаю, что также было сделано, чтобы синтаксис соответствовал переопределению виртуального метода в базовом классе.

public abstract class MyBase
{
    public virtual void MyVirtualMethod() { }

    public virtual void MyOtherVirtualMethod() { }

    public abstract void MyAbtractMethod();
}

public class MyDerived : MyBase
{
    // When overriding a virtual method in MyBase, we use the override keyword.
    public override void MyVirtualMethod() { }

    // If we want to hide the virtual method in MyBase, we use the new keyword.
    public new void MyOtherVirtualMethod() { }

    // Because MyAbtractMethod is abstract in MyBase, we have to override it: 
    // we can't hide it with new.
    // For consistency with overriding a virtual method, we also use the override keyword.
    public override void MyAbtractMethod() { }
}

Таким образом, C # можно было бы спроектировать так, чтобы вам не требовалось ключевое слово override для переопределения абстрактных методов, но я думаю, что дизайнеры решили, что это будет сбивать с толку, поскольку это не будет соответствовать переопределению виртуального метода.

Polyfun
источник
Re: «дизайнеры решили, что это будет сбивать с толку, поскольку это не будет соответствовать переопределению виртуального метода» - да, но больше. Предположим, у вас есть базовый класс B с виртуальным методом M и производный класс D с переопределением. Теперь предположим, что автор B решает сделать M абстрактным. Это серьезное изменение, но, возможно, они так и делают. Вопрос: должен ли автор D быть обязан удалить override? Я думаю, что большинство людей согласится с тем, что абсурдно заставлять автора D вносить ненужные изменения в коде; их класс в порядке!
Эрик Липперт