Como você perguntou por que o C # fez dessa maneira, é melhor perguntar aos criadores do C #. Anders Hejlsberg, o principal arquiteto do C #, respondeu por que eles escolheram não usar virtual por padrão (como em Java) em uma entrevista , os snippets pertinentes estão abaixo.
Lembre-se de que o Java é virtual por padrão com a palavra-chave final para marcar um método como não -virtual. Ainda existem dois conceitos para aprender, mas muitas pessoas não sabem sobre a palavra-chave final ou não usam proativamente. C # obriga a usar o virtual e o novo / substituir para conscientemente tomar essas decisões.
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.
A entrevista tem mais discussões sobre como os desenvolvedores pensam sobre o design de herança de classes e como isso levou à sua decisão.
Agora, para a seguinte pergunta:
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).
Isso ocorre quando uma classe derivada deseja declarar que não obedece ao contrato da classe base, mas possui um método com o mesmo nome. (Para quem não sabe a diferença entre new
e override
em C #, consulte este MSDN página ).
Um cenário muito prático é este:
- Você criou uma API, que tem uma classe chamada
Vehicle
. - Comecei a usar sua API e derivou
Vehicle
. - Sua classe
Vehicle
não tem nenhum métodoPerformEngineCheck()
. - Na minha
Car
classe, adiciono um métodoPerformEngineCheck()
. - Você lançou uma nova versão da sua API e adicionou um
PerformEngineCheck()
. - Não consigo renomear meu método porque meus clientes dependem da API e isso os quebraria.
-
Então, quando eu recompilar contra sua nova API, o C # me avisa sobre esse problema, por exemplo
Se a base
PerformEngineCheck()
não forvirtual
:app2.cs(15,17): warning CS0108: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'. Use the new keyword if hiding was intended.
E se a base
PerformEngineCheck()
forvirtual
: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.
-
Agora, devo tomar uma decisão explícita sobre se minha turma está realmente estendendo o contrato da classe base ou se é um contrato diferente, mas é o mesmo nome.
- Ao torná-lo
new
, não quebro meus clientes se a funcionalidade do método base for diferente do método derivado. Qualquer código que faça referência aVehicle
não veráCar.PerformEngineCheck()
chamado, mas o código que tiver uma referência aCar
continuará a ver a mesma funcionalidade que eu ofereci emPerformEngineCheck()
.
Um exemplo similar é quando outro método na classe base pode estar chamando PerformEngineCheck()
(esp. na versão mais recente), como é que se impede que ele chame o PerformEngineCheck()
da classe derivada? Em Java, essa decisão ficaria na classe base, mas não sabe nada sobre a classe derivada. Em C #, essa decisão fica ambas na classe base (por meio da palavra-chave virtual
), e na classe derivada (por meio das palavras-chave new
e override
).
Naturalmente, os erros que o compilador gera também fornecem uma ferramenta útil para que os programadores não cometam erros inesperados (isto é, substituam ou forneçam novas funcionalidades sem perceber isso).
Como Anders disse, o mundo real nos obriga a questões que, se começássemos do zero, nunca iríamos querer entrar.
EDIT: Adicionado um exemplo de onde new
teria que ser usado para garantir a compatibilidade da interface.
EDIT: Enquanto passando pelos comentários, eu também me deparei com um write-up por Eric Lippert (então um dos membros do comitê de design do C #) em outros cenários de exemplo (mencionados por Brian).
PARTE 2: com base na pergunta atualizada
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?
Quem decide se SpaceShip
está realmente substituindo o Vehicle.accelerate()
ou se é diferente? Tem que ser o desenvolvedor SpaceShip
. Portanto, se SpaceShip
desenvolvedor decidir que eles não estão mantendo o contrato da classe base, sua chamada para Vehicle.accelerate()
não deve ir para SpaceShip.accelerate()
ou deveria? É quando eles vão marcá-lo como new
. No entanto, se eles decidirem que realmente mantêm o contrato, então, de fato, eles o marcarão override
. Em ambos os casos, seu código se comportará corretamente chamando o método correto com base no contrato . Como o seu código pode decidir se SpaceShip.accelerate()
está realmente substituindo Vehicle.accelerate()
ou se é uma colisão de nomes? (Veja meu exemplo acima).
No entanto, no caso de herança implícita, mesmo se SpaceShip.accelerate()
não mantivesse o contrato de Vehicle.accelerate()
, a chamada do método ainda iria para SpaceShip.accelerate()
.