Dato che hai chiesto perché C # è stato così, è meglio chiedere ai creatori di C #. Anders Hejlsberg, l'architetto principale di C #, ha risposto perché ha scelto di non utilizzare il virtual di default (come in Java) in un colloquio , i frammenti pertinenti sono riportati di seguito.
Ricorda che Java è virtuale per impostazione predefinita con la parola chiave finale per contrassegnare un metodo come non -virtuale. Ancora due concetti da imparare, ma molte persone non conoscono la parola chiave finale o non la usano in modo proattivo. C # obbliga a usare virtuale e nuovo / override per prendere consapevolmente queste decisioni.
There are several reasons. One is performance. We can observe that as
people write code in Java, they forget to mark their methods final.
Therefore, those methods are virtual. Because they're virtual, they
don't perform as well. There's just performance overhead associated
with being a virtual method. That's one issue.
A more important issue is versioning. There are two schools of thought
about virtual methods. The academic school of thought says,
"Everything should be virtual, because I might want to override it
someday." The pragmatic school of thought, which comes from building
real applications that run in the real world, says, "We've got to be
real careful about what we make virtual."
When we make something virtual in a platform, we're making an awful
lot of promises about how it evolves in the future. For a non-virtual
method, we promise that when you call this method, x and y will
happen. When we publish a virtual method in an API, we not only
promise that when you call this method, x and y will happen. We also
promise that when you override this method, we will call it in this
particular sequence with regard to these other ones and the state will
be in this and that invariant.
Every time you say virtual in an API, you are creating a call back
hook. As an OS or API framework designer, you've got to be real
careful about that. You don't want users overriding and hooking at any
arbitrary point in an API, because you cannot necessarily make those
promises. And people may not fully understand the promises they are
making when they make something virtual.
L'intervista ha più discussioni su come gli sviluppatori pensano alla progettazione dell'ereditarietà delle classi e su come ciò abbia portato alla loro decisione.
Ora alla seguente domanda:
I'm not able to understand why in the world I'm going to add a method
in my DerivedClass with same name and same signature as BaseClass and
define a new behaviour but at the run-time polymorphism, the BaseClass
method will be invoked! (which is not overriding but logically it
should be).
Questo sarebbe quando una classe derivata vuole dichiarare che non si attiene al contratto della classe base, ma ha un metodo con lo stesso nome. (Per chi non conosce la differenza tra new
e override
in C #, vedi questo MSDN pagina ).
Uno scenario molto pratico è questo:
- Hai creato un'API, che ha una classe chiamata
Vehicle
.
- Ho iniziato a utilizzare la tua API e ho ricavato
Vehicle
.
- La tua classe
Vehicle
non ha avuto alcun metodo PerformEngineCheck()
.
- Nella mia classe
Car
, aggiungo un metodo PerformEngineCheck()
.
- Hai rilasciato una nuova versione della tua API e aggiunto un
PerformEngineCheck()
.
- Non riesco a rinominare il mio metodo perché i miei client dipendono dalla mia API e li romperà.
-
Quindi quando ricompilato la tua nuova API, C # mi avvisa di questo problema, ad es.
Se la base PerformEngineCheck()
non era virtual
:
app2.cs(15,17): warning CS0108: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
Use the new keyword if hiding was intended.
E se la base PerformEngineCheck()
era virtual
:
app2.cs(15,17): warning CS0114: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
-
Ora, devo prendere una decisione esplicita se la mia classe stia effettivamente estendendo il contratto della classe base, o se si tratta di un contratto diverso ma è lo stesso nome.
- Rendendolo
new
, non interrompo i miei clienti se la funzionalità del metodo base era diversa dal metodo derivato. Qualsiasi codice che fa riferimento a Vehicle
non vedrà chiamato Car.PerformEngineCheck()
, ma il codice con un riferimento a Car
continuerà a visualizzare la stessa funzionalità che avevo offerto in PerformEngineCheck()
.
Un esempio simile è quando un altro metodo nella classe base potrebbe chiamare PerformEngineCheck()
(specialmente nella versione più recente), come si impedisce di chiamare il PerformEngineCheck()
della classe derivata? In Java, tale decisione si baserà sulla classe base, ma non conosce nulla sulla classe derivata. In C #, tale decisione riposa entrambi sulla classe base (tramite la parola chiave virtual
), e sulla classe derivata (tramite le parole chiave new
e override
).
Ovviamente, gli errori che genera il compilatore forniscono anche uno strumento utile affinché i programmatori non commettano errori in modo imprevisto (ovvero eseguono l'override o forniscono nuove funzionalità senza rendersene conto.)
Come ha detto Anders, il mondo reale ci costringe a risolvere tali problemi che, se dovessimo ripartire da zero, non vorremmo mai entrare.
EDIT: Aggiunto un esempio di dove new
dovrebbe essere usato per garantire la compatibilità dell'interfaccia.
MODIFICA: passando attraverso i commenti, ho trovato anche un write-up di Eric Lippert (quindi uno dei membri del comitato di progettazione C #) su altri scenari di esempio (citato da Brian).
PARTE 2: basata sulla domanda aggiornata
But in case of C# if SpaceShip does not override the Vehicle class'
accelerate and use new then the logic of my code will be broken. Isn't
that a disadvantage?
Chi decide se SpaceShip
sta effettivamente sovrascrivendo Vehicle.accelerate()
o se è diverso? Deve essere lo sviluppatore SpaceShip
. Quindi se lo sviluppatore SpaceShip
decide che non mantengono il contratto della classe base, la tua chiamata a Vehicle.accelerate()
non dovrebbe andare a SpaceShip.accelerate()
, o dovrebbe? Questo è il momento in cui lo contrassegneranno come new
. Tuttavia, se decidono che effettivamente mantiene il contratto, allora lo contrassegneranno infatti override
. In entrambi i casi, il tuo codice si comporterà correttamente chiamando il metodo corretto in base al contratto . Come può il tuo codice decidere se SpaceShip.accelerate()
stia effettivamente sovrascrivendo Vehicle.accelerate()
o se si tratta di una collisione di nomi? (Vedi il mio esempio sopra).
Tuttavia, nel caso dell'ereditarietà implicita, anche se SpaceShip.accelerate()
non ha mantenuto il contratto di Vehicle.accelerate()
, la chiamata al metodo andrebbe comunque a SpaceShip.accelerate()
.