Ocultación de Métodos en C#
Ocultación de Métodos en C#. Una vez estudiado el modo en el que los métodos se intercambian información a través de parámetros, volvemos al repaso de posibles modificadores que puede preceder a la declaración de un método.
Ocultación de Métodos en C#
Ya hemos hablado de la herencia, esto es, de cómo una clase es capaz de obtener los miembros de una clase base. Sin embargo no sabemos cómo reimplementar en una clase heredada la funcionalidad de un miembro de la base. Para esto precisamente tenemos la instrucción new, teniendo lo siguiente:
using System;
public class Base {
public static void MetodoBase () {
Console.WriteLine ("Yo soy Base.MetodoBase ()");
}
}
public class Heredada : Base {
public static void Main () {
MetodoBase ();
}
new public static void MetodoBase () {
Console.WriteLine ("Yo soy Heredada.MetodoBase ()");
}
}
Tenemos dos clases, donde una hereda de la otra. La segunda además redefine el método MetodoBase() gracias al modificador new. Al ejecutar el programa, se vería lo siguiente:
Yo soy Heredada .MetodoBase ( )
Lo que verifica que la redefinición ha sido un éxito. Si eliminamos el parámetro new el programa seguiría compilando, pero obtendríamos una advertencia:
New. cs ( 18 ) warning CS0108:
The keyword new is required on' Heredada . MetodoBas e ' because it hides inherited member' Base .MetodoBase '
Compilation succeeded - 1 warning(s) s )
El compilador nos ayuda a no ocultar métodos sin querer. Igualmente, si la clase Base no tuviera ninguna definición para MetodoBase(), el compilador también nos advertiría de que no estamos redefiniendo nada. Todo esto se puede complicar un poco más. Supongamos que tenemos una clase B que hereda de A, y una clase C que hereda de B. Pues bien, es posible redefinir un método de A en B, sin que C note el cambio, teniendo lo siguiente:
using System;
public class A {
public static void MetodoBase () {
Console.WriteLine ("Yo soy A.MetodoBase ()");
}
}
public class B : A {
new prívate static void MetodoBase () {
Console.WriteLine ("Yo soy B.MetodoBase ()");
}
}
public class C : B {
static void Main () {
MetodoBase ();
}
}
El método C imprimiría por pantalla "Yo soy A.MetodoBase ()" gracias a que en B hemos marcado la redefinición como privada, lo que la restringe a dicha clase. Si cambiamos prívate por public, o hacemos la llamada dentro de Bconseguiremos "Yo soy B.MetodoBase ()". Tenemos una doble herencia anidada de clases en la que la ocultación de métodos es solamente parcial.
Métodos Virtuales
Una herencia sutilmente diferente pero parecida a la anterior ocurre con los métodos virtuales. En este caso, podemos cambiar el funcionamiento de un método "hacia atrás". Anteriormente vimos como una clase heredada puede implementar su propio método. En este caso, lo que podemos conseguir es que la clase heredada afecte al funcionamiento del método en la clase base. Veamos el siguiente ejemplo:
using System;
public class Base {public void MetodoNormal () {
Console.WriteLine ("Soy Base.MetodoNormal ()");
}
public virtual void MetodoVirtual () {
Console.WriteLine ("Soy Base.MetodoVirtual ()");
}
}
public class Heredada : Base {
new public void MetodoNormal () {
Console.WriteLine ("Soy Heredada.MetodoNormal ()");
}
public override void MetodoVirtual () {
Console.WriteLine ("Soy Heredada.MetodoVirtual ()");
}
static void Main () {
Heredada claseHeredada = new Heredada ();
Base claseBase = claseHeredada;
claseBase.MetodoNormal ();
claseHeredada.MetodoNormal ();
claseBase.MetodoVirtual ();
claseHeredada.MetodoVirtual ();
}
}
Si lo ejecutamos obtenemos lo siguiente:
Soy Base.MetodoNormal ()
Soy Heredada.MetodoNormal ()
Soy Heredada.MetodoVirtual ()
Soy Heredada.MetodoVirtual ()
Nótese como algo extraño sucede en la tercera línea. Mientras que en el código fuente la llamada se hace a la clase Base, el resultado es el de la clase heredada, cosa que no ocurre con el "método normal".
La culpa de este comportamiento la tiene la combinación de las palabras clave virtual y override, que declaran y redefinen respectivamente a los métodos virtuales. En este caso el método escogido no se corresponde con el que observa en el momento de la compilación, sino en el momento de la ejecución. Debemos fijarnos en que la definición de la instancia de la clase base se hace en términos de la clase heredada al comienzo del método principal. Vemos entonces como override hace un papel análogo a new, solo que se ocupa de métodos virtuales, que pueden variar la funcionalidad incluso de las clases bases. Si la declaración de las instancias las cambiamos por las siguientes:
Base claseBase = new Base ();
Heredada claseHeredada = new Heredada ();
Todo funcionará sin diferencias. Los métodos virtuales se comportarán como los demás. Lo divertido sucede cuando volvemos a considerar más de dos clases. ¿Qué ocurrirá en el siguiente ejemplo?
using System;
class A {
public virtual void Metodo () {
Console.WriteLine ("Yo soy A.Metodo ()");
}
}
class B : A {
public override void Metodo () {
Console.WriteLine ("Yo soy B.Metodo ()");
}
}
class C : B {
new public virtual void Metodo () {
Console.WriteLine ("Yo soy C.Metodo ()");
}
}
class D : C {
public override void Metodo () {
Console.WriteLine ("Yo soy D.Metodo ()");
}
static void Main () {
D d = new D ();
A a = d;
B b = d;
C c = d;
a.Metodo ();
b.Metodo ();
c.Metodo ();
d.Metodo ();
}
}
Hemos usado todas las combinaciones posibles. Primero declaramos un método virtual, luego lo redefinimos con override. En C se usa new y se vuelve a emplear virtual, y en D se cierra el ciclo y se utiliza de nuevo override. A continuación creamos una instancia de la clase más baja en el nivel de herencia, y el resto de las instancias las hacemos depender de ella. Como siempre, terminamos llamando a todos los métodos. ¿Qué ocurrirá? Si hemos seguido el razonamiento hasta aquí, encontraremos rápidamente la respuesta: las clases que contengan métodos virtuales perderán su funcionalidad y las demás no. El problema está en a.Metodo() ya que tenemos dos opciones, o bien se queda con el método de B o bien salta hasta D que es la instancia activa. El resultado final es el siguiente, esto es, la clase que se ocupa de la implementación es la más cercana en el árbol de herencias:
Yo soy B.Metodo ( )
Yo soy B.Metodo ( )
Yo soy D.Metodo ( )
Yo soy D.Metodo ( )
Fuente: Revista "Todo Programación". Año 1. Número 6.
Deja una respuesta