Dlaczego przed metodami abstrakcyjnymi wymagane jest zastąpienie słowa kluczowego, gdy implementujemy je w klasie potomnej?

9

Kiedy tworzymy klasę dziedziczącą po klasie abstrakcyjnej i kiedy implementujemy odziedziczoną klasę abstrakcyjną, dlaczego musimy używać słowa kluczowego zastępującego?

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

Ponieważ metoda jest zadeklarowana jako abstrakcyjna w swojej klasie nadrzędnej, nie ma żadnej implementacji w klasie nadrzędnej, więc dlaczego tutaj jest konieczne zastąpienie słowa kluczowego?

psj01
źródło
2
Metoda abstrakcyjna jest domyślnie metodą wirtualną, zgodnie ze specyfikacjami
Pavel Anikhouski
„Modyfikator zastępowania jest wymagany do rozszerzenia lub zmodyfikowania abstrakcyjnej lub wirtualnej implementacji odziedziczonej metody, właściwości, indeksu lub zdarzenia”. docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
gunr2171
2
Ponieważ jeśli tego nie umieścisz, „ukrywasz” metodę, zamiast ją przesłonić. Dlatego pojawia się ostrzeżenie, że „jeśli to jest to, co zamierzałeś, użyj słowa newkluczowego”… Otrzymasz również błąd „żadna metoda nie zastępuje metody klasy bazowej”.
Ron Beyer
@RonBeyer z wirtualną metodą tak, ale ze streszczeniem po prostu nie skompiluje się.
Johnathan Barclay,

Odpowiedzi:

14

Kiedy tworzymy klasę dziedziczącą po klasie abstrakcyjnej i kiedy implementujemy odziedziczoną klasę abstrakcyjną, dlaczego musimy używać słowa kluczowego zastępującego?

"Dlaczego?" na takie pytania może być trudno odpowiedzieć, ponieważ są niejasne. Założę, że twoje pytanie brzmi: „jakie argumenty można sformułować podczas projektowania języka, aby argumentować za tym, że overridesłowo kluczowe jest wymagane ?”

Zacznijmy od cofnięcia się. W niektórych językach, powiedzmy Java, metody są domyślnie wirtualne i zastępowane automatycznie. Projektanci C # byli tego świadomi i uważali to za niewielką wadę w Javie. C # nie jest „Javą z usuniętymi głupimi częściami”, jak niektórzy powiedzieli, ale projektanci C # chętnie uczyli się z problematycznych punktów projektowych w C, C ++ i Javie, a nie replikowali ich w C #.

Projektanci C # uznali przesłonięcie za możliwe źródło błędów; w końcu jest to sposób na zmianę zachowania istniejącego, przetestowanego kodu , co jest niebezpieczne. Zastąpienie nie jest czymś, co należy zrobić przypadkowo lub przypadkowo; powinien być zaprojektowany przez kogoś, kto się nad tym intensywnie zastanawia . Dlatego metody nie są domyślnie wirtualne i dlatego musisz powiedzieć, że zastępujesz metodę.

To podstawowe rozumowanie. Możemy teraz przejść do bardziej zaawansowanego rozumowania.

Odpowiedź StriplingWarrior daje dobre pierwsze cięcie w tworzeniu bardziej zaawansowanego argumentu. Autor klasy pochodnej może nie być poinformowany o klasie bazowej, może chcieć stworzyć nową metodę i nie powinniśmy pozwolić użytkownikowi na pomyłkę .

Chociaż ten punkt jest uzasadniony, istnieje wiele kontrargumentów, takich jak:

  • Autor klasy pochodnej ma obowiązek wiedzieć wszystko o klasie bazowej! Ponownie wykorzystują ten kod i powinni dołożyć należytej staranności, aby dokładnie zrozumieć ten kod przed ponownym użyciem.
  • W twoim konkretnym scenariuszu metoda wirtualna jest abstrakcyjna; błędem byłoby nie nadpisanie go, więc jest mało prawdopodobne, że autor stworzy implementację przez przypadek.

Zróbmy jeszcze bardziej zaawansowany argument na ten temat. W jakich okolicznościach autor klasy pochodnej może być usprawiedliwiony za to, że nie wie, co robi klasa podstawowa? Rozważ ten scenariusz:

  • Autor klasy podstawowej tworzy abstrakcyjną klasę podstawową B.
  • Autor klasy pochodnej, w innym zespole, tworzy klasę pochodną D metodą M.
  • Autor klasy podstawowej zdaje sobie sprawę, że zespoły, które rozszerzają klasę podstawową B zawsze będą musiały podać metodę M, więc autor klasy podstawowej dodaje metodę abstrakcyjną M.
  • Co się stanie po ponownej kompilacji klasy D.

Chcemy, aby autor D został poinformowany, że coś istotnego się zmieniło . Istotną rzeczą, która się zmieniła, jest to, że M jest teraz wymogiem i że ich implementacja musi być przeciążona. DM może potrzebować zmienić swoje zachowanie, gdy wiemy, że można go wywołać z klasy podstawowej. Właściwe jest, aby nie mówić po cichu „och, DM istnieje i rozszerza BM”. Prawidłowe działanie kompilatora to błąd i powiedz „hej, autor D, sprawdź to założenie, które nie jest już poprawne i popraw kod, jeśli to konieczne”.

W twoim przykładzie załóżmy, że opcjaoverride była włączona, ponieważ zastępuje metodę abstrakcyjną. Istnieją dwie możliwości: (1) autor kodu zamierza zastąpić metodę abstrakcyjną lub (2) metoda zastępująca jest nadpisywana przypadkowo, ponieważ ktoś inny zmienił klasę podstawową, a kod jest teraz w jakiś subtelny sposób niepoprawny. Nie możemy rozróżnić tych możliwości, jeśli jest to opcjonalne . SayHellooverride

Ale jeśli overridejest to wymagane , możemy wyróżnić trzy scenariusze. Jeśli istnieje możliwy błąd w kodzie, overrideto go brakuje . Jeśli jest to celowo nadpisywane, overridejest obecne . A jeśli celowo nie jest to nadrzędne, to newjest obecne . Projekt C # umożliwia nam dokonanie tych subtelnych rozróżnień.

Pamiętaj, że raportowanie błędów kompilatora wymaga przeczytania opinii programisty ; kompilator musi wywnioskować z niewłaściwego kodu, jaki prawidłowy kod prawdopodobnie miał na myśli autor , i podać błąd wskazujący właściwy kierunek. Im więcej wskazówek możemy pozostawić programistom w kodzie na temat tego, co myślą, tym lepiej kompilator może wykonać raportowanie błędów, a tym samym szybciej można znaleźć i naprawić swoje błędy.

Ale bardziej ogólnie, C # został zaprojektowany dla świata, w którym zmienia się kod . Istnieje wiele funkcji C #, które wydają się „nieparzyste”, ponieważ informują one programistę, gdy założenie, które było ważne, stało się nieważne z powodu zmiany klasy podstawowej. Ta klasa błędów nosi nazwę „kruchych awarii klasy bazowej”, a język C # ma wiele interesujących elementów łagodzących dla tej klasy błędów.

Eric Lippert
źródło
4
Dziękuję za opracowanie. Zawsze doceniam twoje odpowiedzi, zarówno dlatego, że dobrze jest mieć autorytatywny głos od kogoś „wewnątrz”, a także dlatego, że wykonujesz świetną robotę, wyjaśniając wszystko w prosty i kompletny sposób.
StriplingWarrior
Dziękujemy za szczegółowe wyjaśnienie! naprawdę to doceniam.
psj01,
Świetne punkty! Czy mógłbyś wymienić niektóre inne wymienione przez Ciebie środki zaradcze?
aksh1618
3

Określa, czy próbujesz zastąpić inną metodę w klasie nadrzędnej, czy utworzyć nową implementację unikalną dla tego poziomu hierarchii klas. Można sobie wyobrazić, że programista może nie być świadomy istnienia metody w klasie nadrzędnej z dokładnie taką samą sygnaturą, jak ta, którą tworzą w swojej klasie, co może prowadzić do przykrych niespodzianek.

Chociaż prawdą jest, że metoda abstrakcyjna musi zostać zastąpiona w nieabstrakcyjnej klasie potomnej, twórcy C # prawdopodobnie uznali, że lepiej jest wyraźnie powiedzieć o tym, co próbujesz zrobić.

StriplingWarrior
źródło
To dobry początek na zrozumieniu rozumowania zespołu projektantów języków; Dodałem odpowiedź, która pokazuje, jak zespół zaczyna od pomysłu, który tu wyraziłeś, ale idzie o krok dalej.
Eric Lippert,
1

Ponieważ abstractmetoda jest metodą wirtualną bez implementacji, zgodnie ze specyfikacją języka C # , oznacza, że ​​metoda abstrakcyjna jest niejawnie metodą wirtualną. I overridejest stosowany w celu rozszerzenia lub modyfikacji abstrakcyjnego lub wirtualnego wdrożenia, jak można zobaczyć tutaj

Aby sformułować to nieco inaczej - używasz metod wirtualnych do implementacji pewnego rodzaju późnego wiązania, podczas gdy metody abstrakcyjne zmuszają podklasy tego typu do jawnego zastąpienia metody. To jest punkt, gdy metoda jest virtual, że mogą być pominięte, gdy jest to abstract- to musi być nadpisane

Pavel Anikhouski
źródło
1
Myślę, że pytanie nie dotyczy tego, co robi, ale dlaczego musi być wyraźne.
Johnathan Barclay,
0

Aby dodać do odpowiedzi @ StriplingWarrior, myślę, że zrobiono to również, aby mieć składnię zgodną z przesłonięciem metody wirtualnej w klasie bazowej.

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

Tak więc C # można było zaprojektować tak, aby nie trzeba było słowa kluczowego zastępującego w celu zastąpienia metod abstrakcyjnych, ale myślę, że projektanci zdecydowali, że byłoby to mylące, ponieważ nie byłoby spójne z zastąpieniem metody wirtualnej.

Polyfun
źródło
Re: „projektanci zdecydowali, że byłoby to mylące, ponieważ nie byłoby to zgodne z zastąpieniem metody wirtualnej” - tak, ale więcej. Załóżmy, że masz klasę bazową B z wirtualną metodą M i pochodną klasę D z przesłonięciem. Załóżmy teraz, że autor B decyduje się na streszczenie M. To przełomowa zmiana, ale może to robią. Pytanie: czy autor D powinien zostać zobowiązany do usunięcia override? Myślę, że większość ludzi zgodzi się, że absurdalne jest zmuszanie autora D do wprowadzenia niepotrzebnej zmiany kodu; ich klasa jest w porządku!
Eric Lippert,