qué polimorfismo polimorfico herencia form encapsulamiento ejemplos clases abstractas c# .net oop inheritance polymorphism

polimorfico - polimorfismo en c# windows form



¿Por qué este código polimórfico C#imprime lo que hace? (5)

Recientemente recibí la siguiente pieza de código como una especie de rompecabezas para ayudar a entender el Polymorphism y la Inheritance en OOP - C #.

// No compiling! public class A { public virtual string GetName() { return "A"; } } public class B:A { public override string GetName() { return "B"; } } public class C:B { public new string GetName() { return "C"; } } void Main() { A instance = new C(); Console.WriteLine(instance.GetName()); } // No compiling!

Ahora, después de una larga y larga conversación con el otro desarrollador que presentó el acertijo, sé cuál es el resultado, pero no lo estropearé. El único problema que realmente tengo es cómo llegamos a esa salida, cómo avanza el código, qué hereda qué, etc.

Pensé que se devolvería C ya que esa parece ser la clase que se define. Luego, se me pasó por la cabeza si B sería devuelto porque C hereda B , pero B también hereda A (¡que es donde me confundí!).

Pregunta:

¿Alguien podría explicar cómo el polimorfismo y la herencia juegan su parte en la recuperación de la salida, eventualmente mostrada en la pantalla?


Debería devolver "B" porque B.GetName() se mantiene en el pequeño cuadro de tabla virtual para la función A.GetName() . C.GetName() es una "anulación" de tiempo de compilación, no anula la tabla virtual por lo que no puede recuperarla a través de un puntero a A



Fácil, solo tienes que tener en cuenta el árbol de herencia.

En su código, usted tiene una referencia a una clase de tipo ''A'', que está instanciada por una instancia de tipo ''C''. Ahora, para resolver la dirección de método exacta para el método virtual ''GetName ()'', el compilador sube la jerarquía de herencia y busca la anulación más reciente (tenga en cuenta que solo ''virtual'' es una anulación, ''nuevo'' es algo completamente diferente ...).

Eso es en resumen lo que sucede. La nueva palabra clave del tipo ''C'' solo jugaría un rol si la llamaras en una instancia de tipo ''C'' y el compilador negaría por completo todas las posibles relaciones de herencia. Hablando estrictamente, esto no tiene nada que ver con el polimorfismo. Puedes verlo por el hecho de que si enmascaras un método virtual o no virtual con la palabra clave ''nueva'' no hace ninguna diferencia ...

''Nuevo'' en la clase ''C'' significa exactamente eso: si llama a ''GetName ()'' en una instancia de este tipo (exacto), entonces olvide todo y use ESTE método. ''Virtual'' en sentido contrario: suba al árbol de herencia hasta que encuentre un método con este nombre, sin importar el tipo exacto de la instancia de llamada.


La forma correcta de pensar sobre esto es imaginar que cada clase requiere que sus objetos tengan un cierto número de "espacios"; esas máquinas tragamonedas están llenas de métodos. La pregunta "¿qué método realmente se llama?" requiere que descubras dos cosas:

  1. ¿Cuáles son los contenidos de cada ranura?
  2. ¿Qué ranura se llama?

Comencemos considerando las máquinas tragamonedas. Hay dos máquinas tragamonedas Todas las instancias de A deben tener una ranura que llamaremos GetNameSlotA. Se requiere que todas las instancias de C tengan una ranura que llamaremos GetNameSlotC. Eso es lo que significa "nuevo" en la declaración en C, significa "Quiero un nuevo tragamonedas". En comparación con la "anulación" en la declaración en B, lo que significa "No quiero una nueva ranura, quiero volver a utilizar GetNameSlotA".

Por supuesto, C hereda de A, entonces C también debe tener un slot GetNameSlotA. Por lo tanto, las instancias de C tienen dos espacios: GetNameSlotA y GetNameSlotC. Las instancias de A o B que no son C tienen una ranura, GetNameSlotA.

Ahora, ¿qué entra en esas dos ranuras cuando creas una nueva C? Hay tres métodos, que llamaremos GetNameA, GetNameB y GetNameC.

La declaración de A dice "pon GetNameA en GetNameSlotA". A es una superclase de C, por lo que la regla de A se aplica a C.

La declaración de B dice "poner GetNameB en GetNameSlotA". B es una superclase de C, por lo que la regla de B se aplica a las instancias de C. Ahora tenemos un conflicto entre A y B. B es el tipo más derivado, por lo que gana: la regla de B anula la regla de A. De ahí la palabra "anular" en la declaración.

La declaración de C dice "poner GetNameC en GetNameSlotC".

Por lo tanto, su nueva C tendrá dos ranuras. GetNameSlotA contendrá GetNameB y GetNameSlotC contendrá GetNameC.

Ya hemos determinado qué métodos están en qué ranuras, así que hemos respondido a nuestra primera pregunta.

Ahora tenemos que responder la segunda pregunta. ¿Qué ranura se llama?

Piense en ello como si fuera el compilador. Usted tiene una variable. Lo único que sabe es que es del tipo A. Se le pide que resuelva una llamada a método sobre esa variable. Miras las ranuras disponibles en una A, y la única ranura que puedes encontrar que coincide es GetNameSlotA. No sabe acerca de GetNameSlotC, porque solo tiene una variable de tipo A; ¿Por qué buscará máquinas tragamonedas que solo se apliquen a C?

Por lo tanto, esta es una llamada a lo que sea que esté en GetNameSlotA. Ya hemos determinado que, en tiempo de ejecución, GetNameB estará en esa ranura. Por lo tanto, esta es una llamada a GetNameB.

El punto clave aquí es que en C # la resolución de sobrecarga elige una ranura y genera una llamada a lo que sea que esté en esa ranura.


OK, la publicación es un poco vieja, pero es una pregunta excelente y una excelente respuesta, así que solo quería agregar mis pensamientos.

Considere el siguiente ejemplo, que es el mismo que antes, a excepción de la función principal:

// No compiling! public class A { public virtual string GetName() { return "A"; } } public class B:A { public override string GetName() { return "B"; } } public class C:B { public new string GetName() { return "C"; } } void Main() { Console.Write ( "Type a or c: " ); string input = Console.ReadLine(); A instance = null; if ( input == "a" ) instance = new A(); else if ( input == "c" ) instance = new C(); Console.WriteLine( instance.GetName() ); } // No compiling!

Ahora es muy obvio que la llamada a la función no puede vincularse a una función específica en tiempo de compilación. Sin embargo, se debe compilar algo, y esa información solo puede depender del tipo de referencia. Por lo tanto, sería imposible ejecutar la función GetName de la clase C con cualquier referencia que no sea de tipo C.

PD: Tal vez debería haber usado el término método en lugar de función, pero como dijo Shakespeare: una función con cualquier otro nombre sigue siendo una función :)