Puisque vous avez demandé pourquoi C # l'avait fait de cette façon, il est préférable de demander aux créateurs de C #. Anders Hejlsberg, l'architecte principal de C #, a expliqué pourquoi ils avaient choisi de ne pas utiliser le mode virtuel par défaut (comme en Java) dans une interview . Vous trouverez ci-dessous des extraits pertinents.
Gardez à l'esprit que Java utilise virtuel par défaut avec le mot-clé final pour marquer une méthode comme non virtuelle. Encore deux concepts à apprendre, mais beaucoup de gens ne connaissent pas le mot-clé final ou ne l'utilisent pas de manière proactive. C # oblige l'utilisateur à utiliser virtuel et new / override pour prendre ces décisions consciemment.
Il y a plusieurs raisons. L'un est la performance . Nous pouvons observer que lorsque les gens écrivent du code en Java, ils oublient de marquer leurs méthodes comme final. Par conséquent, ces méthodes sont virtuelles. Parce qu'ils sont virtuels, ils ne fonctionnent pas aussi bien. Il y a juste une surcharge de performances associée au fait d'être une méthode virtuelle. C'est un problème.
Un problème plus important est celui des versions . Il existe deux écoles de pensée sur les méthodes virtuelles. L'école de pensée académique dit: "Tout devrait être virtuel, parce que je pourrais vouloir le remplacer un jour." L'école de pensée pragmatique, qui découle de la création d'applications réelles qui fonctionnent dans le monde réel, dit: "Nous devons faire très attention à ce que nous rendons virtuel."
Lorsque nous faisons quelque chose de virtuel sur une plate-forme, nous faisons énormément de promesses quant à son évolution future. Pour une méthode non virtuelle, nous vous promettons que lorsque vous appelez cette méthode, x et y se produiront. Lorsque nous publions une méthode virtuelle dans une API, nous ne promettons pas seulement que lorsque vous appelez cette méthode, x et y se produiront. Nous promettons également que lorsque vous substituez cette méthode, nous l'appellerons dans cette séquence particulière en ce qui concerne ces autres et que l'état sera dans tel ou tel invariant.
Chaque fois que vous dites virtuel dans une API, vous créez un hook de rappel. En tant que concepteur de structure de système d'exploitation ou d'API, vous devez faire très attention à ce sujet. Vous ne voulez pas que les utilisateurs surchargent et accrochent à un moment quelconque d'une API, car vous ne pouvez pas nécessairement faire ces promesses. Et les gens peuvent ne pas comprendre pleinement les promesses qu'ils font lorsqu'ils font quelque chose de virtuel.
L'interview aborde plus en détail la façon dont les développeurs envisagent la conception de l'héritage de classe et comment cela a conduit à leur décision.
Passons maintenant à la question suivante:
Je ne suis pas en mesure de comprendre pourquoi, dans le monde, je vais ajouter une méthode dans mon DerivedClass avec le même nom et la même signature que BaseClass et définir un nouveau comportement, mais au polymorphisme d'exécution, la méthode BaseClass sera invoquée! (ce qui n'est pas primordial mais cela devrait être logique).
Cela se produit lorsqu'une classe dérivée veut déclarer qu'elle ne respecte pas le contrat de la classe de base, mais utilise une méthode portant le même nom. (Pour ceux qui ne connaissent pas la différence entre new
et override
en C #, voir cette page MSDN ).
Un scénario très pratique est le suivant:
- Vous avez créé une API, qui a une classe appelée
Vehicle
.
- J'ai commencé à utiliser votre API et dérivé
Vehicle
.
- Votre
Vehicle
classe n'avait aucune méthode PerformEngineCheck()
.
- Dans ma
Car
classe, j'ajoute une méthode PerformEngineCheck()
.
- Vous avez publié une nouvelle version de votre API et ajouté un
PerformEngineCheck()
.
- Je ne peux pas renommer ma méthode car mes clients sont dépendants de mon API et cela les endommagerait.
Ainsi, lorsque je recompile avec votre nouvelle API, C # me met en garde contre ce problème, par exemple
Si la base PerformEngineCheck()
n'était pas virtual
:
app2.cs(15,17): warning CS0108: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
Use the new keyword if hiding was intended.
Et si la base PerformEngineCheck()
était 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.
Maintenant, je dois explicitement décider si ma classe étend réellement le contrat de la classe de base ou s'il s'agit d'un contrat différent mais qui se trouve être le même nom.
- En le faisant
new
, je ne casse pas mes clients si la fonctionnalité de la méthode de base était différente de la méthode dérivée. Tout code référencé Vehicle
ne sera pas Car.PerformEngineCheck()
appelé, mais le code contenant une référence Car
continuera de voir la même fonctionnalité que celle que j'avais proposée PerformEngineCheck()
.
Un exemple similaire est lorsqu'une autre méthode de la classe de base peut appeler PerformEngineCheck()
(en particulier dans la version la plus récente), comment l'empêche-t-elle d'appeler le PerformEngineCheck()
de la classe dérivée? En Java, cette décision incomberait à la classe de base, mais elle ne sait rien de la classe dérivée. En C #, cette décision repose à la fois sur la classe de base (via le virtual
mot - clé) et sur la classe dérivée (via les mots new
- override
clés et ).
Bien sûr, les erreurs que le compilateur génère constituent également un outil utile pour les programmeurs afin de ne pas commettre d'erreur inopinément (c'est-à-dire, remplacer ou fournir de nouvelles fonctionnalités sans s'en rendre compte.)
Comme Anders l’a dit, le monde réel nous force à nous attaquer à de telles questions que nous ne voudrions jamais aborder si nous partions de zéro.
EDIT: Ajout d'un exemple où il new
faudrait utiliser pour assurer la compatibilité des interfaces.
EDIT: En parcourant les commentaires, je suis également tombé sur un article écrit par Eric Lippert (alors membre du comité de conception de C #) sur d’autres exemples de scénarios (mentionnés par Brian).
PARTIE 2: Basé sur la question mise à jour
Mais dans le cas de C # si SpaceShip ne substitue pas la classe Vehicle à accélérer et à utiliser new, la logique de mon code sera brisée. N'est-ce pas un inconvénient?
Qui décide si SpaceShip
est prépondérant en fait la Vehicle.accelerate()
ou si elle est différente? Ce doit être le SpaceShip
développeur. Ainsi, si le SpaceShip
développeur décide de ne pas conserver le contrat de la classe de base, votre appel Vehicle.accelerate()
ne doit pas être adressé SpaceShip.accelerate()
ou doit-il l'être? C'est à ce moment qu'ils le marqueront comme new
. Toutefois, s’ils décident que le contrat est effectivement conservé, ils le marqueront en fait override
. Dans les deux cas, votre code se comportera correctement en appelant la méthode correcte en fonction du contrat . Comment votre code peut-il décider s'il SpaceShip.accelerate()
s'agit d'une substitution Vehicle.accelerate()
ou s'il s'agit d'une collision de noms? (Voir mon exemple ci-dessus).
Cependant, en cas d'héritage implicite, même si SpaceShip.accelerate()
le contrat de n'est pas conservé Vehicle.accelerate()
, l'appel de la méthode ira quand même à SpaceShip.accelerate()
.