Por que é necessário substituir a palavra-chave na frente dos métodos abstratos quando os implementamos em uma classe filho?

9

Quando criamos uma classe que herda de uma classe abstrata e quando implementamos a classe abstrata herdada, por que precisamos usar a palavra-chave 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();
    }
}

Como o método é declarado abstrato em sua classe pai, ele não possui nenhuma implementação na classe pai, então por que a substituição da palavra-chave é necessária aqui?

psj01
fonte
2
Um método abstrato é implicitamente um método virtual, por especificações
Pavel Anikhouski 20/12/19
"O modificador de substituição é necessário para estender ou modificar a implementação abstrata ou virtual de um método, propriedade, indexador ou evento herdado." docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
gunr2171
2
Porque se você não o colocar lá, estará "ocultando" o método em vez de substituí-lo. É por isso que você recebe o aviso de que "se é isso que você pretendia, utilize o newkeyword` ... Você também deseja obter o 'erro nenhum método substitui o método classe base'.
Ron Beyer
@ RonBeyer com um método virtual sim, mas com resumo, ele simplesmente não seria compilado.
Johnathan Barclay

Respostas:

14

Quando criamos uma classe que herda de uma classe abstrata e quando implementamos a classe abstrata herdada, por que precisamos usar a palavra-chave override?

"Por quê?" perguntas como essa podem ser difíceis de responder porque são vagas. Vou assumir que sua pergunta é "que argumentos poderiam ser feitos durante o design da linguagem para argumentar a posição de que a overridepalavra-chave é necessária ?"

Vamos começar dando um passo atrás. Em algumas linguagens, por exemplo, Java, os métodos são virtuais por padrão e substituídos automaticamente. Os designers de C # estavam cientes disso e consideraram que era uma falha menor no Java. O C # não é "Java com as partes estúpidas retiradas", como alguns disseram, mas os designers do C # estavam interessados ​​em aprender com os pontos problemáticos de design do C, C ++ e Java, e não replicá-los no C #.

Os designers de C # consideraram a substituição uma possível fonte de erros; afinal, é uma maneira de alterar o comportamento do código testado e existente , e isso é perigoso. Substituir não é algo que deve ser feito casualmente ou por acidente; deve ser projetado por alguém pensando bem sobre isso . É por isso que os métodos não são virtuais por padrão e é necessário que você diga que está substituindo um método.

Esse é o raciocínio básico. Agora podemos entrar em um raciocínio mais avançado.

A resposta de StriplingWarrior fornece um bom primeiro corte para se fazer um argumento mais avançado. O autor da classe derivada pode não estar informado sobre a classe base, pode estar pretendendo criar um novo método e não devemos permitir que o usuário substitua por engano .

Embora esse ponto seja razoável, existem vários contra-argumentos, como:

  • O autor de uma classe derivada tem a responsabilidade de saber tudo sobre a classe base! Eles estão reutilizando esse código e devem fazer a devida diligência para entendê-lo completamente antes de reutilizá-lo.
  • No seu cenário específico, o método virtual é abstrato; seria um erro não substituí-lo e, portanto, é improvável que o autor esteja criando uma implementação por acidente.

Vamos então fazer um argumento ainda mais avançado sobre esse ponto. Em que circunstâncias o autor de uma classe derivada pode ser desculpado por não saber o que a classe base faz? Bem, considere este cenário:

  • O autor da classe base cria uma classe base abstrata B.
  • O autor da classe derivada, em uma equipe diferente, cria uma classe D derivada com o método M.
  • O autor da classe base percebe que as equipes que estendem a classe base B sempre precisarão fornecer um método M, portanto, o autor da classe base adiciona o método abstrato M.
  • Quando a classe D é recompilada, o que acontece?

O que queremos que aconteça é que o autor de D seja informado de que algo relevante mudou . O relevante foi que M agora é um requisito e que sua implementação deve ser sobrecarregada. O DM pode precisar alterar seu comportamento, uma vez que sabemos que ele pode ser chamado a partir da classe base. A coisa certa a fazer é não dizer silenciosamente "oh, o DM existe e estende o BM". A coisa correta para o compilador fazer é falhar e diga "ei, autor de D, verifique essa suposição que não é mais válida e corrija seu código, se necessário".

No seu exemplo, suponha que o overrideera opcional no SayHelloporque ele está substituindo um método abstrato. Existem duas possibilidades: (1) o autor do código pretende substituir um método abstrato ou (2) o método de substituição é substituído por acidente porque alguém alterou a classe base e o código agora está errado de alguma maneira sutil. Não podemos distinguir essas possibilidades se overridefor opcional .

Mas se overridefor necessário , em seguida, podemos distinguir três cenários. Se houver um possível erro no código, overrideestá faltando . Se for intencionalmente substituindo, overrideestará presente . E se intencionalmente é não substituindo, em seguida, newé presente . O design do C # nos permite fazer essas distinções sutis.

Lembre-se de que o relatório de erros do compilador requer a leitura da mente do desenvolvedor ; o compilador deve deduzir do código errado o código correto que o autor provavelmente tinha em mente e fornecer um erro que os aponte na direção correta. Quanto mais pistas pudermos fazer com que o desenvolvedor deixe no código o que eles estavam pensando, melhor o trabalho que o compilador pode fazer ao relatar erros e, portanto, mais rápido você pode encontrar e corrigir seus erros.

Mas, de maneira mais geral, o C # foi projetado para um mundo em que o código é alterado . Muitos recursos do C # que parecem "ímpares" existem porque informam o desenvolvedor quando uma suposição que costumava ser válida se tornou inválida porque uma classe base foi alterada. Essa classe de erros é chamada de "falhas frágeis da classe base" e o C # possui várias mitigações interessantes para essa classe de falha.

Eric Lippert
fonte
4
Obrigado por elaborar. Eu sempre aprecio suas respostas, tanto porque é bom ter uma voz autorizada de alguém "por dentro" quanto porque você faz um ótimo trabalho em explicar as coisas de maneira simples e completa.
precisa
Obrigado pela explicação detalhada! realmente aprecio isso.
Psj01 23/12/19
Ótimos pontos! Você poderia listar algumas das outras atenuações mencionadas?
aksh1618 17/01
3

É para especificar se você está tentando substituir outro método na classe pai ou criar uma nova implementação exclusiva para esse nível da hierarquia de classes. É concebível que um programador possa não estar ciente da existência de um método em uma classe pai com exatamente a mesma assinatura que ele cria em sua classe, o que pode levar a surpresas desagradáveis.

Embora seja verdade que um método abstrato deva ser substituído em uma classe filho não abstrata, os criadores de C # provavelmente sentiram que ainda é melhor ser explícito sobre o que você está tentando fazer.

StriplingWarrior
fonte
Este é um bom começo para entender o raciocínio da equipe de design de idiomas; Adicionei uma resposta que mostra como a equipe começa com a idéia que você expressou aqui, mas dá um passo adiante.
Eric Lippert
1

Como abstractmétodo é um método virtual sem implementação, por especificação da linguagem C # , significa que o método abstrato é implicitamente um método virtual. E overrideé usado para estender ou modificar a implementação abstrata ou virtual, como você pode ver aqui

Para reformular um pouco - você usa métodos virtuais para implementar algum tipo de ligação tardia, enquanto métodos abstratos forçam as subclasses do tipo a terem o método explicitamente substituído. Esse é o ponto, quando o método é virtual, ele pode ser substituído, quando é abstract- deve ser substituído

Pavel Anikhouski
fonte
11
Penso que a questão não é sobre o que faz, mas porque deve ser explícita.
precisa
0

Para adicionar à resposta do @ StriplingWarrior, acho que também foi feito para ter uma sintaxe consistente com a substituição de um método virtual na 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() { }
}

Portanto, o C # poderia ter sido projetado para que você não precisasse da palavra-chave override para substituir métodos abstratos, mas acho que os designers decidiram que isso seria confuso, pois não seria consistente com a substituição de um método virtual.

Polyfun
fonte
Re: "os designers decidiram que isso seria confuso, pois não seria consistente com a substituição de um método virtual" - sim, mas mais. Suponha que você tenha uma classe base B com um método virtual M e uma classe derivada D com uma substituição. Agora, suponha que o autor de B decida tornar M abstrato. Essa é uma mudança radical, mas talvez eles o façam. Pergunta: o autor de D deve ser obrigado a remover o override? Acho que a maioria das pessoas concorda que é um absurdo forçar o autor de D a fazer uma alteração desnecessária no código; a aula deles é boa!
Eric Lippert