method how c# inheritance override

how - C#método anular resolución rareza



sealed c# (2)

Esta es la regla, y puede que no te guste ...

Cita de Eric Lippert

si cualquier método en una clase más derivada es un candidato aplicable, es automáticamente mejor que cualquier método en una clase menos derivada, incluso si el método menos derivado tiene una mejor coincidencia de firma.

La razón es porque el método (que es una mejor coincidencia de firma) podría haberse agregado en una versión posterior y, por lo tanto, estar introduciendo un error de " clase base frágil "

Nota : Esta es una parte bastante complicada / en profundidad de las especificaciones de C # y salta por todas partes. Sin embargo, las partes principales del problema que está experimentando se escriben de la siguiente manera

Actualizar

Y esta es la razón por la que me gusta el stackoverflow. Es un gran lugar para aprender y recibir algunas de las mentes más inteligentes de la industria, listas para compartir sus conocimientos y corregir malentendidos, y siempre estoy dispuesto y honrado de ser corregido por ellos. Este es un ejemplo perfecto.

Estaba citando la sección sobre el procesamiento en tiempo de ejecución de la llamada al método . Donde la pregunta es acerca de la resolución de sobrecarga de tiempo de compilación y debería ser.

7.6.5.1 Invocaciones de métodos.

...

El conjunto de métodos candidatos se reduce para contener solo los métodos de la mayoría de los tipos derivados: para cada método CF en el conjunto, donde C es el tipo en el que se declara el método F, todos los métodos declarados en un tipo base de C se eliminan de el conjunto Además, si C es un tipo de clase que no sea un objeto, todos los métodos declarados en un tipo de interfaz se eliminan del conjunto. (Esta última regla solo tiene efecto cuando el grupo de métodos fue el resultado de una búsqueda de miembros en un parámetro de tipo que tiene una clase base efectiva distinta de un objeto y un conjunto de interfaces efectivas no vacías).

Consulte la respuesta de la publicación de Eric https://stackoverflow.com/a/52670391/1612975 para obtener un detalle completo de lo que está pasando aquí y la parte correspondiente de las especificaciones

Original

Especificación de lenguaje C # versión 5.0

7.5.5 Invocación de miembro de función

...

El procesamiento en tiempo de ejecución de una invocación de miembro de función consta de los siguientes pasos, donde M es el miembro de función y, si M es un miembro de instancia, E es la expresión de instancia:

...

Si M es un miembro de la función de instancia declarado en un tipo de referencia:

  • Se evalúa e. Si esta evaluación provoca una excepción, no se ejecutarán más pasos.
  • La lista de argumentos se evalúa como se describe en §7.5.1.
  • Si el tipo de E es un tipo de valor, se realiza una conversión de boxeo (§4.3.1) para convertir E en objeto de tipo, y se considera que E es de tipo objeto en los siguientes pasos. En este caso, M solo podría ser miembro de System.Object.
  • El valor de E se comprueba para ser válido. Si el valor de E es nulo, se lanza una excepción System.NullReferenceException y no se ejecutan más pasos.
  • La función de implementación del miembro a invocar se determina:
    • Si el tipo de tiempo de enlace de E es una interfaz, el miembro de la función a invocar es la implementación de M proporcionada por el tipo de tiempo de ejecución de la instancia a la que hace referencia E. Este miembro de la función se determina aplicando las reglas de mapeo de interfaz (§13.4.4) para determinar la implementación de M proporcionada por el tipo de tiempo de ejecución de la instancia a la que hace referencia E.
    • De lo contrario, si M es un miembro de función virtual, el miembro de función a invocar es la implementación de M proporcionada por el tipo de tiempo de ejecución de la instancia a la que hace referencia E. Este miembro de función se determina aplicando las reglas para determinar la implementación más derivada ( §10.6.3) de M con respecto al tipo de tiempo de ejecución de la instancia a la que hace referencia E.
    • De lo contrario, M es un miembro de función no virtual, y el miembro de función a invocar es M en sí mismo.

Después de leer las especificaciones, lo que es interesante es que, si utiliza una interfaz que describe el método, el compilador elegirá la firma de sobrecarga, funcionando a su vez como se espera.

public interface ITest { void Foo(int x); }

Que se puede ver aquí

En lo que respecta a la interfaz, tiene sentido al considerar que el comportamiento de sobrecarga se implementó para proteger contra la clase base Brittle

Recursos adicionales

Eric Lippert, mas cerca es mejor

El aspecto de la resolución de sobrecarga en C # del que quiero hablar hoy es realmente la regla fundamental por la cual se considera que una sobrecarga potencial es mejor que otra para un sitio de llamadas dado: más cerca siempre es mejor que más lejos. Hay varias formas de caracterizar la "cercanía" en C #. Comencemos con lo más cercano y salgamos:

  • Un primer método declarado en una clase derivada está más cerca que un primer método declarado en una clase base.
  • Un método en una clase anidada está más cerca que un método en una clase contenedora.
  • Cualquier método del tipo de recepción es más cercano que cualquier método de extensión.
  • Un método de extensión encontrado en una clase en un espacio de nombres anidado es más cercano que un método de extensión encontrado en una clase en un espacio de nombres externo.
  • Un método de extensión encontrado en una clase en el espacio de nombres actual está más cerca que un método de extensión encontrado en una clase en un espacio de nombres mencionado por una directiva using.
  • Un método de extensión encontrado en una clase en un espacio de nombres mencionado en una directiva de uso donde la directiva está en un espacio de nombres anidado es más cercano que un método de extensión encontrado en una clase en un espacio de nombres mencionado en una directiva de uso donde la directiva está en un espacio de nombres externo.

Considere el siguiente fragmento de código:

using System; class Base { public virtual void Foo(int x) { Console.WriteLine("Base.Foo(int)"); } } class Derived : Base { public override void Foo(int x) { Console.WriteLine("Derived.Foo(int)"); } public void Foo(object o) { Console.WriteLine("Derived.Foo(object)"); } } public class Program { public static void Main() { Derived d = new Derived(); int i = 10; d.Foo(i); } }

Y el resultado sorprendente es:

Derived.Foo(object)

Espero que seleccione el método Foo(int x) anulado, ya que es más específico. Sin embargo, el compilador de C # elige la versión no heredada de Foo(object o) . Esto también provoca una operación de boxeo.

¿Cuál es la razón de este comportamiento?


La respuesta aceptada es correcta (excepto por el hecho de que cita la sección incorrecta de la especificación), pero explica las cosas desde la perspectiva de la especificación en lugar de justificar por qué la especificación es buena.

Supongamos que tenemos una clase B de base y una clase D derivada. B tiene un método M que toma Jirafa. Ahora, recuerde, por supuesto, el autor de D sabe todo sobre los miembros públicos y protegidos de B. Dicho de otra manera: el autor de D debe saber más que el autor de B, porque D se escribió después de B , y D se escribió para extender B a un escenario que B aún no ha manejado . Por lo tanto, debemos confiar en que el autor de D está haciendo un mejor trabajo de implementación de todas las funciones de D que el autor de B.

Si el autor de D hace una sobrecarga de M que toma un animal, están diciendo que sé mejor que el autor de B cómo tratar con los animales, y eso incluye a las jirafas . Debemos esperar una resolución de sobrecarga cuando recibimos una llamada a DM (Giraffe) para llamar a DM (Animal), y no a BM (Giraffe).

Pongámoslo de otra manera: tenemos dos posibles justificaciones:

  • Una llamada a DM (Giraffe) debe ir a BM (Giraffe) porque Giraffe es más específica que Animal
  • Una llamada a DM (jirafa) debe ir a DM (Animal) porque D es más específica que B

Ambas justificaciones tienen que ver con la especificidad , entonces, ¿qué justificación es mejor? ¡No estamos llamando a ningún método en Animal ! Estamos llamando al método en D, por lo que la especificidad debe ser la que gana. La especificidad del receptor es mucho, mucho más importante que la especificidad de cualquiera de sus parámetros. Los tipos de parámetros están ahí para romper el empate . Lo importante es asegurarse de elegir el receptor más específico porque ese método fue escrito más tarde por alguien con más conocimiento del escenario que D pretende manejar .

Ahora, podría decir, ¿qué pasa si el autor de D también ha anulado BM (jirafa)? Hay dos argumentos por los que una llamada a DM (Giraffe) debería llamar a DM (Animal) en este caso.

Primero , el autor de D debe saber que se puede llamar a DM (Animal) con una jirafa , y que debe escribirse para que haga lo correcto . Por lo tanto, no debe importar desde la perspectiva del usuario si la llamada se resuelve a DM (Animal) o BM (Giraffe), porque D se ha escrito correctamente para hacer lo correcto.

En segundo lugar , si el autor de D ha reemplazado un método de B o no es un detalle de implementación de D, y no es parte del área de superficie pública . Dicho de otra manera: sería muy extraño si al cambiar si un método se ha invalidado o no , se elige el método . Imagínese si está llamando a un método en alguna clase base en una versión, y luego, en la próxima versión, el autor de la clase base realiza un cambio menor en cuanto a si un método está anulado o no; no esperaría que la resolución de sobrecarga en la clase derivada cambiara. C # ha sido diseñado cuidadosamente para evitar este tipo de falla.