¿Por qué se requiere tener una palabra clave de anulación frente a los métodos abstractos cuando los implementamos en una clase secundaria?

9

Cuando creamos una clase que hereda de una clase abstracta y cuando implementamos la clase abstracta heredada, ¿por qué tenemos que usar la palabra clave de anulación?

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

Dado que el método se declara abstracto en su clase principal, no tiene ninguna implementación en la clase principal, entonces, ¿por qué es necesaria la anulación de palabras clave aquí?

psj01
fuente
2
Un método abstracto es implícitamente un método virtual, según las especificaciones
Pavel Anikhouski
"Se requiere el modificador de anulación para extender o modificar la implementación abstracta o virtual de un método, propiedad, indexador o evento heredado". docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
gunr2171
2
Porque si no lo pones allí, estás "ocultando" el método en lugar de anularlo. Es por eso que recibe la advertencia de que "si eso es lo que pretendía, use la newpalabra clave '... También obtendrá el error" ningún método anula el método de la clase base ".
Ron Beyer
@RonBeyer con un método virtual sí, pero con resumen simplemente no se compilaría.
Johnathan Barclay

Respuestas:

14

Cuando creamos una clase que hereda de una clase abstracta y cuando implementamos la clase abstracta heredada, ¿por qué tenemos que usar la palabra clave de anulación?

"¿Por qué?" preguntas como esta pueden ser difíciles de responder porque son vagas. Asumiré que su pregunta es "¿qué argumentos podrían hacerse durante el diseño del lenguaje para argumentar la posición de que overridese requiere la palabra clave ?"

Comencemos dando un paso atrás. En algunos lenguajes, digamos, Java, los métodos son virtuales por defecto y se anulan automáticamente. Los diseñadores de C # eran conscientes de esto y lo consideraban un defecto menor en Java. C # no es "Java con las partes estúpidas extraídas" como algunos han dicho, pero los diseñadores de C # estaban ansiosos por aprender de los puntos problemáticos de diseño de C, C ++ y Java, y no replicarlos en C #.

Los diseñadores de C # consideraron la anulación como una posible fuente de errores; después de todo, es una forma de cambiar el comportamiento del código existente y probado , y eso es peligroso. La anulación no es algo que deba hacerse de manera casual o accidental; debe ser diseñado por alguien que lo piense mucho . Es por eso que los métodos no son virtuales de manera predeterminada, y por qué debe decir que está anulando un método.

Ese es el razonamiento básico. Ahora podemos entrar en un razonamiento más avanzado.

La respuesta de StriplingWarrior da un buen primer corte al hacer un argumento más avanzado. El autor de la clase derivada puede estar desinformado acerca de la clase base, puede tener la intención de crear un nuevo método y no debemos permitir que el usuario anule por error .

Aunque este punto es razonable, hay una serie de argumentos en contra, como:

  • ¡El autor de una clase derivada tiene la responsabilidad de saber todo sobre la clase base! Están reutilizando ese código, y deben hacer la diligencia debida para comprender ese código a fondo antes de volver a usarlo.
  • En su escenario particular, el método virtual es abstracto; sería un error no anularlo, por lo que es poco probable que el autor cree una implementación por accidente.

Entonces hagamos un argumento aún más avanzado sobre este punto. ¿En qué circunstancias se puede excusar al autor de una clase derivada por no saber qué hace la clase base? Bueno, considere este escenario:

  • El autor de la clase base crea una clase base abstracta B.
  • El autor de la clase derivada, en un equipo diferente, crea una clase D derivada con el método M.
  • El autor de la clase base se da cuenta de que los equipos que extienden la clase base B siempre necesitarán proporcionar un método M, por lo que el autor de la clase base agrega el método abstracto M.
  • Cuando se vuelve a compilar la clase D, ¿qué sucede?

Lo que queremos que suceda es que el autor de D es informado de que algo relevante ha cambiado . Lo relevante que ha cambiado es que M es ahora un requisito y que su implementación debe sobrecargarse. Es posible que DM necesite cambiar su comportamiento una vez que sepamos que podría llamarse desde la clase base. Lo correcto es no decir en silencio "oh, DM existe y extiende BM". Lo correcto para el compilador es fallar y decir "oye, autor de D, revisa esta suposición que ya no es válida y arregla tu código si es necesario".

En su ejemplo, supongamos que el overrideera opcional en SayHelloporque es reemplazar un método abstracto. Hay dos posibilidades: (1) el autor del código tiene la intención de anular un método abstracto, o (2) el método de anulación se anula por accidente porque alguien más cambió la clase base, y el código ahora está mal de alguna manera sutil. No podemos distinguir estas posibilidades si overridees opcional .

Pero si overridees necesario , podemos distinguir tres escenarios. Si hay un posible error en el código, a continuación overridese faltante . Si se anula intencionalmente, entonces overrideestá presente . Y si es intencional no anulando entonces newes presente . El diseño de C # nos permite hacer estas sutiles distinciones.

Recuerde que los informes de errores del compilador requieren leer la mente del desarrollador ; el compilador debe deducir del código incorrecto qué código correcto probablemente tenía el autor en mente , y dar un error que los señala en la dirección correcta. Cuantas más pistas podamos hacer que el desarrollador deje en el código lo que estaban pensando, mejor será el trabajo que el compilador puede hacer al informar errores y, por lo tanto, más rápido podrá encontrar y corregir sus errores.

Pero, en general, C # fue diseñado para un mundo en el que el código cambia . De hecho, hay una gran cantidad de características de C # que parecen "extrañas" porque informan al desarrollador cuando una suposición que solía ser válida se volvió inválida porque cambió una clase base. Esta clase de errores se llama "fallas de clase base frágiles", y C # tiene una serie de mitigaciones interesantes para esta clase de falla.

Eric Lippert
fuente
44
Gracias por elaborar Siempre agradezco sus respuestas, tanto porque es bueno tener una voz autorizada de alguien "en el interior" como porque hace un gran trabajo al explicar las cosas de manera simple y completa.
StriplingWarrior
Gracias por la explicación en profundidad! realmente lo aprecio.
psj01
Grandes puntos! ¿Podría enumerar algunas de las otras mitigaciones que menciona?
aksh1618
3

Es para especificar si está intentando anular otro método en la clase principal o crear una nueva implementación única para este nivel de la jerarquía de clases. Es concebible que un programador no sea consciente de la existencia de un método en una clase principal con exactamente la misma firma que el creado en su clase, lo que podría generar sorpresas desagradables.

Si bien es cierto que un método abstracto debe anularse en una clase secundaria no abstracta, los creadores de C # probablemente sintieron que aún es mejor ser explícito sobre lo que está tratando de hacer.

StriplingWarrior
fuente
Este es un buen comienzo para comprender el razonamiento del equipo de diseño del lenguaje; Agregué una respuesta que muestra cómo el equipo comienza con la idea que has expresado aquí, pero va un paso más allá.
Eric Lippert
1

Debido a que el abstractmétodo es un método virtual sin implementación, según la especificación del lenguaje C # , significa que el método abstracto es implícitamente un método virtual. Y overridese usa para extender o modificar la implementación abstracta o virtual, como puede ver aquí

Para reformularlo un poco, utiliza métodos virtuales para implementar algún tipo de enlace tardío, mientras que los métodos abstractos obligan a las subclases del tipo a anular explícitamente el método. Ese es el punto, cuando el método es virtual, puede ser anulado, cuando es un abstract- debe ser anulado

Pavel Anikhouski
fuente
1
Creo que la pregunta no es sobre lo que hace, sino por qué tiene que ser explícito.
Johnathan Barclay
0

Para agregar a la respuesta de @ StriplingWarrior, creo que también se hizo para tener una sintaxis que sea consistente con la anulación de un método virtual en la clase 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() { }
}

Por lo tanto, C # podría haber sido diseñado para que no necesitara la palabra clave de anulación para anular métodos abstractos, pero creo que los diseñadores decidieron que sería confuso ya que no sería coherente con anular un método virtual.

Polyfun
fuente
Re: "los diseñadores decidieron que sería confuso ya que no sería consistente con anular un método virtual" - sí, pero más. Suponga que tiene una clase base B con un método virtual M y una clase D derivada con una anulación. Ahora supongamos que el autor de B decide hacer M abstracto. Es un cambio radical, pero quizás lo hagan. Pregunta: ¿se debe exigir al autor de D que elimine el override? Creo que la mayoría de la gente estaría de acuerdo en que es absurdo obligar al autor de D a hacer un cambio de código innecesario; su clase esta bien!
Eric Lippert