Чому потрібно, щоб ключове слово було замінено перед абстрактними методами, коли ми реалізуємо їх у дочірньому класі?


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();
    }
}

Оскільки метод оголошений абстрактним у своєму батьківському класі, він не має жодної реалізації у батьківському класі, тож чому тут потрібне переопределення ключового слова?


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 методом М.
  • Автор базового класу розуміє, що командам, які розширюють базовий клас B, завжди потрібно буде запропонувати метод M, тому автор базового класу додає абстрактний метод М.
  • Коли клас D перекомпілюється, що відбувається?

Що ми хочемо, щоб автор D повідомив, що щось відповідне змінилося . Відповідне, що змінилося, - це те, що М тепер є вимогою і що їх реалізація повинна бути перевантажена. DM може знадобитися змінити свою поведінку, як тільки ми дізнаємось, що його можна викликати з базового класу. Правильна річ - це не мовчки говорити "о, DM існує і розширює BM". Правильна річ, яку повинен зробити компілятор, - це невдача , і скажіть: "Ей, авторе D, перевіри це твоє припущення, яке більше не дійсне, і за необхідності виправити свій код".

У вашому прикладі, припустимо , що overrideбуло необов'язково на , SayHelloтому що це перевизначення абстрактний метод. Існує дві можливості: (1) автор коду має намір замінити абстрактний метод, або (2) метод переосмислення відміняється випадково, тому що хтось інший змінив базовий клас, і код зараз помилився дещо тонко. Ми не можемо розрізнити ці можливості, якщо overrideце необов'язково .

Але якщо overrideце потрібно, то ми можемо розрізнити три сценарії. Якщо в коді можлива помилка, то overrideвона відсутня . Якщо це навмисно переосмислюється, тоді overrideвоно присутнє . І якщо він навмисно НЕ перекриваючи то newє присутній . Дизайн C # дозволяє нам зробити ці тонкі відмінності.

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

Але загалом, C # був розроблений для світу, в якому змінюється код . Дуже багато функцій C #, які здаються "непарними", насправді є, оскільки вони повідомляють розробника, коли припущення, яке раніше було дійсним , стає недійсним, оскільки змінився базовий клас. Цей клас помилок називають "крихкими збитками базового класу", а C # має ряд цікавих заходів щодо зменшення цього класу.


4
Дякуємо за детальну розробку. Я завжди ціную ваші відповіді, тому що добре мати авторитетний голос від когось "зсередини" і тому, що ви чудово працюєте, пояснюючи речі просто і повністю.
StriplingWarrior

Дякую за глибоке пояснення! дуже ціную це.
psj01

Чудові бали! Чи можете ви перелічити деякі інші пом'якшення, які ви згадуєте?
aksh1618

3

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

Хоча це правда, що абстрактний метод повинен бути відмінений у неабстракційному дочірньому класі, розробники C #, ймовірно, вважають, що все-таки краще бути чітким щодо того, що ви намагаєтеся зробити.


Це вдалий початок з розуміння міркувань команди з мовної розробки; Я додав відповідь, яка показує, як команда починає з ідеї, яку ви висловили тут, але робить її на крок далі.
Ерік Ліпперт

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 для переосмислення абстрактних методів, але я думаю, що дизайнери вирішили, що це буде заплутано, оскільки це не буде відповідати виправданню віртуального методу.


Re: "дизайнери вирішили, що це буде заплутано, оскільки це не буде суперечити переосмисленню віртуального методу" - так, але більше. Припустимо, у вас базовий клас B з віртуальним методом M і похідний клас D з переопределенням. Тепер припустимо, що автор B вирішує зробити М абстрактним. Це суттєва зміна, але, можливо, вони так і роблять. Запитання: чи потрібно від автора D видалити override? Я думаю, що більшість людей погодиться, що абсурдно змушувати автора D вносити зайві зміни коду; їх клас чудово!
Ерік Ліпперт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.