Perché è necessario avere la parola chiave override davanti ai metodi astratti quando li implementiamo in una classe figlio?


9

Quando creiamo una classe che eredita da una classe astratta e quando implementiamo la classe astratta ereditata, perché dobbiamo usare la parola chiave 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();
    }
}

Poiché il metodo è dichiarato astratto nella sua classe genitore non ha alcuna implementazione nella classe genitore, quindi perché la sostituzione della parola chiave è necessaria qui?


2
Un metodo astratto è implicitamente un metodo virtuale, secondo le specifiche
Pavel Anikhouski,


"Il modificatore di override è necessario per estendere o modificare l'implementazione astratta o virtuale di un metodo, proprietà, indicizzatore o evento ereditato." docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
gunr2171

2
Perché se non lo metti lì, stai "nascondendo" il metodo invece di sovrascriverlo. Ecco perché ricevi l'avvertimento che "se è quello che volevi, usa la newparola chiave` ... Avresti anche l'errore" nessun metodo sovrascrive il metodo della classe base ".
Ron Beyer

@RonBeyer con un metodo virtuale sì, ma con abstract semplicemente non si compila.
Johnathan Barclay,

Risposte:


14

Quando creiamo una classe che eredita da una classe astratta e quando implementiamo la classe astratta ereditata, perché dobbiamo usare la parola chiave override?

"Perché?" a domande come questa può essere difficile rispondere perché sono vaghe. Presumo che la tua domanda sia "quali argomenti potrebbero essere fatti durante la progettazione del linguaggio per sostenere la posizione richiesta dalla overrideparola chiave ?"

Cominciamo facendo un passo indietro. In alcune lingue, ad esempio Java, i metodi sono virtuali per impostazione predefinita e sovrascritti automaticamente. I progettisti di C # erano consapevoli di questo e lo consideravano un piccolo difetto in Java. C # no "Java con le parti stupide tolte" come alcuni hanno detto, ma i progettisti di C # erano desiderosi di imparare dai punti di progettazione problematici di C, C ++ e Java, e non replicarli in C #.

I progettisti di C # hanno considerato l'override come una possibile fonte di bug; dopo tutto, è un modo per cambiare il comportamento del codice esistente e testato , e questo è pericoloso. L'override non è qualcosa che dovrebbe essere fatto casualmente o per caso; dovrebbe essere progettato da qualcuno che ci pensa intensamente . Ecco perché i metodi non sono virtuali per impostazione predefinita e perché ti viene richiesto di sostituire un metodo.

Questo è il ragionamento di base. Ora possiamo andare in qualche ragionamento più avanzato.

La risposta di StriplingWarrior offre un buon primo taglio nel formulare un argomento più avanzato. L'autore della classe derivata potrebbe non essere informato sulla classe di base, potrebbe essere intenzionato a creare un nuovo metodo e non dovremmo consentire all'utente di eseguire l'override per errore .

Sebbene questo punto sia ragionevole, ci sono una serie di controargomenti, come:

  • L'autore di una classe derivata ha la responsabilità di sapere tutto sulla classe base! Stanno riutilizzando quel codice e dovrebbero fare la dovuta diligenza per comprendere a fondo quel codice prima di riutilizzarlo.
  • Nel tuo particolare scenario il metodo virtuale è astratto; sarebbe un errore non sovrascriverlo, quindi è improbabile che l'autore stia creando un'implementazione per caso.

Facciamo quindi un argomento ancora più avanzato su questo punto. In quali circostanze l'autore di una classe derivata può essere scusato per non sapere cosa fa la classe base? Bene, considera questo scenario:

  • L'autore della classe base crea una classe base astratta B.
  • L'autore della classe derivata, in un team diverso, crea una classe D derivata con il metodo M.
  • L'autore della classe base comprende che i team che estendono la classe base B dovranno sempre fornire un metodo M, quindi l'autore della classe base aggiunge il metodo astratto M.
  • Quando viene ricompilata la classe D, cosa succede?

Ciò che vogliamo che accada è che l'autore di D sia informato che qualcosa di rilevante è cambiato . La cosa rilevante che è cambiata è che M è ora un requisito e che la loro implementazione deve essere sovraccaricata. DM potrebbe aver bisogno di cambiare comportamento una volta che sappiamo che potrebbe essere chiamato dalla classe base. La cosa giusta da fare è non dire in silenzio "oh, DM esiste ed estende BM". La cosa corretta da fare per il compilatore è fallire e dire "hey, autore di D, controlla questa tua ipotesi che non è più valida e correggi il tuo codice se necessario".

Nel tuo esempio, supponi che l' opzioneoverride fosse attiva perché sta sovrascrivendo un metodo astratto. Esistono due possibilità: (1) l'autore del codice intende sovrascrivere un metodo astratto, oppure (2) il metodo prevalente sta prevalendo per caso perché qualcun altro ha cambiato la classe base e il codice ora è sbagliato in qualche modo sottile. Non possiamo distinguere queste possibilità se è facoltativo . SayHellooverride

Ma se overrideè necessario, possiamo distinguere tre scenari. Se c'è un possibile errore nel codice poi overrideè mancante . Se è intenzionalmente ignorato, allora overrideè presente . E se è intenzionalmente non override allora newè presente . Il design di C # ci consente di fare queste sottili distinzioni.

Ricorda che la segnalazione degli errori del compilatore richiede di leggere la mente dello sviluppatore ; il compilatore deve dedurre dal codice errato quale codice corretto probabilmente aveva in mente l'autore e fornire un errore che li indichi nella direzione corretta. Più indizi possiamo fare in modo che lo sviluppatore lasci nel codice ciò che stava pensando, migliore è un lavoro che il compilatore può fare nella segnalazione degli errori e quindi più veloce è possibile trovare e correggere i bug.

Ma più in generale, C # è stato progettato per un mondo in cui il codice cambia . Molte funzionalità di C # che appaiono "dispari" sono in realtà lì perché informano lo sviluppatore quando un presupposto che era valido è diventato non valido perché una classe base è cambiata. Questa classe di bug è chiamata "errori di classe base fragili" e C # ha un numero di mitigazioni interessanti per questa classe di errore.


4
Grazie per l'elaborazione. Apprezzo sempre le tue risposte, sia perché è bello avere una voce autorevole da qualcuno "all'interno" sia perché fai un ottimo lavoro nel spiegare le cose in modo semplice e completo.
StriplingWarrior il

Grazie per la spiegazione approfondita! lo apprezzo davvero.
psj01

Grandi punti! Potresti elencare alcune delle altre mitigazioni che menzioni?
aksh1618,

3

È specificare se si sta tentando di sovrascrivere un altro metodo nella classe genitore o creare una nuova implementazione univoca per questo livello della gerarchia di classi. È concepibile che un programmatore potrebbe non essere consapevole dell'esistenza di un metodo in una classe genitore con esattamente la stessa firma di quello che creano nella loro classe, il che potrebbe portare a brutte sorprese.

Mentre è vero che un metodo astratto deve essere ignorato in una classe figlio non astratta, gli artigiani di C # probabilmente hanno pensato che fosse ancora meglio essere espliciti su ciò che stai cercando di fare.


Questo è un buon inizio per comprendere il ragionamento del team di progettazione linguistica; Ho aggiunto una risposta che mostra come la squadra inizia con l'idea che hai espresso qui, ma fa un passo avanti.
Eric Lippert,

1

Poiché il abstractmetodo è un metodo virtuale senza implementazione, secondo le specifiche del linguaggio C # , significa che il metodo astratto è implicitamente un metodo virtuale. E override'usato per estendere o modificare l'implementazione astratta o virtuale, come puoi vedere qui

Per riformularlo un po ', usi i metodi virtuali per implementare un qualche tipo di associazione tardiva, mentre i metodi astratti costringono le sottoclassi del tipo ad avere il metodo esplicitamente ignorato. Questo è il punto, quando il metodo è virtual, può essere ignorato, quando è un abstract- deve essere ignorato


1
Penso che la domanda non riguardi ciò che fa, ma perché debba essere esplicito.
Johnathan Barclay,

0

Per aggiungere alla risposta di @ StriplingWarrior, penso che sia stato fatto anche per avere una sintassi coerente con l'override di un metodo virtuale nella classe base.

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

Quindi C # avrebbe potuto essere progettato in modo da non aver bisogno della parola chiave override per l'override dei metodi astratti, ma penso che i progettisti abbiano deciso che sarebbe stato fonte di confusione in quanto non sarebbe coerente con l'override di un metodo virtuale.


Ri: "i progettisti hanno deciso che sarebbe confuso in quanto non sarebbe coerente con l'override di un metodo virtuale" - sì, ma di più. Supponiamo di avere una classe base B con un metodo virtuale M e una classe derivata D con una sostituzione. Supponiamo ora che l'autore di B decida di rendere M astratto. È un cambiamento decisivo, ma forse lo fanno. Domanda: l'autore di D dovrebbe essere tenuto a rimuovere il override? Penso che la maggior parte delle persone sarebbe d'accordo sul fatto che sia assurdo costringere l'autore di D a fare una modifica non necessaria del codice; la loro classe va bene!
Eric Lippert,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.