lenguaje - ¿Por qué este código de C#devuelve lo que hace
instrucciones en programacion ejemplos (3)
¿Puede alguien ayudarme a entender por qué este fragmento de código devuelve "Bar-Bar-Quux"? Me está costando entender esto incluso después de leer en las interfaces.
interface IFoo
{
string GetName();
}
class Bar : IFoo
{
public string GetName() { return "Bar"; }
}
class Baz : Bar
{
public new string GetName() { return "Baz"; }
}
class Quux : Bar, IFoo
{
public new string GetName() { return "Quux"; }
}
class Program
{
static void Main()
{
Bar f1 = new Baz();
IFoo f2 = new Baz();
IFoo f3 = new Quux();
Console.WriteLine(f1.GetName() + "-" + f2.GetName() + "-" + f3.GetName());
}
}
Hay dos cosas pasando aquí. Uno es miembro de la clandestinidad. Esto es bastante conocido y cubierto en elsewhere . La otra característica menos conocida es la reimplementación de la interfaz cubierta en la sección 13.4.6 de la especificación C # 5. Citar:
A una clase que hereda una implementación de interfaz se le permite volver a implementar la interfaz incluyéndola en la lista de clases base. Una reimplementación de una interfaz sigue exactamente las mismas reglas de mapeo de interfaz que una implementación inicial de una interfaz. Por lo tanto, la asignación de interfaz heredada no tiene ningún efecto en la asignación de interfaz establecida para la reimplementación de la interfaz.
y
Las declaraciones de miembro público heredado y las declaraciones de miembros de interfaz explícita heredadas participan en el proceso de asignación de interfaz para interfaces reimplementadas.
El resultado para f1.GetName()
es "Bar" porque el método Baz.GetName
está ocultando Bar.GetName
y f1
se declara como tipo Bar
. No hay despacho a la implementación del tipo de tiempo de ejecución a menos que se declare explícitamente como virtual y se invalide.
Del mismo modo, para f2.GetName()
, Baz.GetName
está ocultando la implementación en Bar
, por lo que no se llama cuando se utiliza el envío a través de una referencia a la interfaz. La interfaz está "asignada" al método declarado en Bar
porque ese es el tipo en el que se declaró la interfaz. No importa que Baz
tenga un método compatible con el mismo nombre. Las reglas para el mapeo de interfaz se definen en la sección 13.4.4 de la especificación. Si GetName
hubiera declarado virtual en Bar
, podría anularse, y luego se llamaría a través de la interfaz. El resultado es por lo tanto también "Bar".
Para f3.GetName()
, Quux
vuelve a implementar IFoo
para que pueda definir su propia asignación a GetName
. Tenga en cuenta que también oculta la implementación heredada de Bar
. No es necesario usar nuevo para hacer la reimplementación, simplemente suprime la advertencia sobre la ocultación. Por lo tanto, el resultado es "Quux".
Entonces eso explica el resultado que ves: "Bar-Bar-Quux"
Esta post de Eric Lippert analiza algunos matices más en esta complicada función.
La salida es Bar-Bar-Quux como resultado de 3 llamadas a GetName () en su llamada al método Console.WriteLine.
Bar f1 = new Baz();
IFoo f2 = new Baz();
IFoo f3 = new Quux();
Console.WriteLine(f1.GetName() + "-" + f2.GetName() + "-" + f3.GetName());
//Bar-Bar-Quux
Examinemos cada llamada para que quede más claro lo que sucede.
f1.GetName ()
f1
se instancia como Baz
. Sin embargo , está escrito como Bar
. Como Bar
expone GetName
, cuando se f1.GetName()
, ese es el método que se llama, independientemente del hecho de que Baz
también implemente GetName
. La razón es que f1
no está escrito como Baz
, y si lo fuera, llamaría GetName
método GetName
Baz
. Un ejemplo de eso sería examinar el resultado de
Console.WriteLine(((Baz)f1).GetName() + "-" + f2.GetName() + "-" + f3.GetName());
//Baz-Bar-Quux
Esto es posible debido a dos hechos. Primero, f1
se instanciaba inicialmente como Baz
, simplemente se tipeaba como Bar
. En segundo lugar, Baz
tiene un método GetName
, y el uso de new
en su definición oculta el método GetName
heredado de la Bar
permitiendo que se GetName
a GetName
Baz
.
f2.GetName ()
Se está produciendo una tipificación muy similar con f2
, que se define como
IFoo f2 = new Baz();
Si bien la clase Baz implementa un método GetName
, no implementa el método GetName
porque Baz
no hereda de IFoo
y, por lo tanto, el método no está disponible. Bar
implementa IFoo
, y como Baz
hereda de Bar
, GetName
Bar
es el método que se expone cuando f2
se escribe como IFoo
.
Nuevamente, dado que f2
inicialmente se instanciaba como Baz
, aún se puede convertir a Baz
.
Console.WriteLine(f1.GetName() + "-" + ((Baz)f2).GetName() + "-" + f3.GetName());
//Bar-Baz-Quux
Y tendrá el mismo resultado de salida por el motivo indicado anteriormente para f1
( f2
se tipeó originalmente como Baz
, y el método GetName
Baz
oculta el método GetName
heredado de la Bar
).
f3.GetName ()
Otra historia aquí. Quux
hereda e implementa IFoo
, y oculta la implementación de IFoo
de IFoo
mediante el uso de new
. El resultado es que el método GetName
Quux
es el que se llama.
Las interfaces, por definición, no tienen una implementación asociada, lo que quiere decir que sus métodos son siempre virtuales y abstractos. Por el contrario, la Bar
clase anterior define una implementación concreta para GetName
. Esto satisface el contrato requerido para implementar IFoo
.
Class Baz
ahora hereda de Bar
y declara un new
método GetName
. Es decir, la clase padre Bar
tiene un método con el mismo nombre, pero se ignora por completo cuando se trabaja con objetos Baz
explícitamente.
Sin embargo, si un objeto Baz
se lanza como una Bar
, o simplemente se asigna a una variable de tipo Bar
o IFoo
, hará lo que le digan y se comportará como una Bar
. En otras palabras, el nombre de método GetName
refiere a Bar.GetName
lugar de Baz.GetName
.
Ahora, en el tercer caso, Quux
hereda de Bar
e implementa IFoo
. Ahora, cuando se presenta como IFoo
, proporcionará su propia implementación (de acuerdo con la especificación proporcionada en la respuesta de Mike Z).
Sin embargo, cuando un Quux se lanza como una barra, devuelve "Barra", al igual que Baz.