Considera este código:
class Program
{
static void Main(string[] args)
{
Person person = new Teacher();
person.ShowInfo();
Console.ReadLine();
}
}
public class Person
{
public void ShowInfo()
{
Console.WriteLine("I am Person");
}
}
public class Teacher : Person
{
public new void ShowInfo()
{
Console.WriteLine("I am Teacher");
}
}
Cuando ejecuto este código, se muestra lo siguiente:
Soy persona
Sin embargo, puede ver que es una instancia de Teacher
, no de Person
. ¿Por qué el código hace eso?
c#
class
derived-class
azulado
fuente
fuente
Respuestas:
Hay una diferencia entre
new
yvirtual
/override
.Puede imaginar que una clase, cuando se instancia, no es más que una tabla de punteros, que apunta a la implementación real de sus métodos. La siguiente imagen debería visualizar esto bastante bien:
Ahora hay diferentes formas, se puede definir un método. Cada uno se comporta de manera diferente cuando se usa con herencia. La forma estándar siempre funciona como lo ilustra la imagen de arriba. Si desea cambiar este comportamiento, puede adjuntar diferentes palabras clave a su método.
1. Clases abstractas
La primera de ellas es
abstract
.abstract
los métodos simplemente apuntan a ninguna parte:Si su clase contiene miembros abstractos, también debe marcarse como
abstract
, de lo contrario, el compilador no compilará su aplicación. No puede crear instancias deabstract
clases, pero puede heredar de ellas y crear instancias de sus clases heredadas y acceder a ellas utilizando la definición de clase base. En su ejemplo, esto se vería así:Si se llama, el comportamiento de
ShowInfo
varía, en función de la implementación:Ambos,
Student
s yTeacher
s sonPerson
s, pero se comportan de manera diferente cuando se les pide que soliciten información sobre ellos mismos. Sin embargo, la forma de pedirles que soliciten su información es la misma: usar laPerson
interfaz de clase.Entonces, ¿qué sucede detrás de escena, cuando heredas de
Person
? Al implementarShowInfo
, el puntero ya no apunta a ninguna parte , ¡ahora apunta a la implementación real! Al crear unaStudent
instancia, apunta aStudent
sShowInfo
:2. Métodos virtuales
La segunda forma es usar
virtual
métodos. El comportamiento es el mismo, excepto que proporciona una implementación predeterminada opcional en su clase base. Las clases convirtual
miembros pueden instanciarse, sin embargo, las clases heredadas pueden proporcionar implementaciones diferentes. Así es como debería verse realmente su código para funcionar:La diferencia clave es que el miembro base
Person.ShowInfo
ya no apunta a ninguna parte . Esta es también la razón por la que puede crear instancias dePerson
(y, por lo tanto, no es necesario que se marqueabstract
más):Debes notar que esto no se ve diferente de la primera imagen por ahora. Esto se debe a que el
virtual
método apunta a una implementación "de la manera estándar ". Usandovirtual
, puede decirPersons
, que pueden (no deben ) proporcionar una implementación diferente paraShowInfo
. Si proporciona una implementación diferente (usandooverride
), como lo hice para loTeacher
anterior, la imagen se vería igual que paraabstract
. Imagínese, no proporcionamos una implementación personalizada paraStudent
s:El código se llamaría así:
Y la imagen para
Student
se vería así:3. La palabra clave mágica `new` también conocida como" Sombrear "
new
Es más un truco alrededor de esto. Puede proporcionar métodos en clases generalizadas, que tengan los mismos nombres que los métodos en la clase / interfaz base. Ambos apuntan a su propia implementación personalizada:La implementación se parece a la que usted proporcionó. El comportamiento difiere según la forma en que accede al método:
Este comportamiento puede ser deseado, pero en su caso es engañoso.
¡Espero que esto te aclare las cosas!
fuente
new
el que se rompe la herencia de la función y hace que la nueva función separada de la superclase funciónPerson
, noStudent
;)El polimorfismo de subtipo en C # usa virtualidad explícita, similar a C ++ pero a diferencia de Java. Esto significa que debe marcar explícitamente los métodos como reemplazables (es decir
virtual
). En C # también debe marcar explícitamente los métodos de anulación como anulación (es deciroverride
) para evitar errores tipográficos.En el código en su pregunta, usted usa
new
, que hace sombra en lugar de anular. El sombreado simplemente afecta la semántica en tiempo de compilación en lugar de la semántica en tiempo de ejecución, de ahí el resultado no deseado.fuente
Debe hacer que el método sea virtual y debe anular la función en la clase secundaria, para llamar al método del objeto de clase que coloca en la referencia de la clase principal.
Métodos virtuales
Usando Nuevo para Sombrear
Está utilizando una nueva palabra clave en lugar de anular, esto es lo que hace new
Si el método en la clase derivada no está precedido por palabras clave nuevas o anuladas, el compilador emitirá una advertencia y el método se comportará como si la nueva palabra clave estuviera presente.
Si el método en la clase derivada está precedido por la nueva palabra clave, el método se define como independiente del método en la clase base , este artículo de MSDN lo explica muy bien.
Unión temprana VS unión tardía
Tenemos un enlace temprano en tiempo de compilación para el método normal (no virtual), que es el caso actual en el que el compilador vinculará la llamada al método de la clase base que es un método de tipo de referencia (clase base) en lugar de que el objeto se mantenga en la referencia de la base clase, es decir, objeto de clase derivada . Esto se debe a
ShowInfo
que no es un método virtual. El enlace tardío se realiza en tiempo de ejecución para (método virtual / anulado) utilizando la tabla de métodos virtuales (vtable).fuente
Quiero construir a partir de la respuesta de Achratt . Para completar, la diferencia es que el OP está esperando el
new
palabra clave en el método de la clase derivada anule el método de la clase base. Lo que realmente hace es ocultar el método de la clase base.En C #, como se menciona en otra respuesta, la anulación del método tradicional debe ser explícita; el método de la clase base debe marcarse como
virtual
y la clase derivada debe ser específicamenteoverride
el método de la clase base. Si se hace esto, no importa si el objeto se trata como una instancia de la clase base o clase derivada; el método derivado se encuentra y se llama. Esto se hace de manera similar a C ++; un método marcado "virtual" o "anulación", cuando se compila, se resuelve "tarde" (en tiempo de ejecución) determinando el tipo real del objeto referenciado y atravesando la jerarquía de objetos hacia abajo a lo largo del árbol desde el tipo variable hasta el tipo de objeto real, para encontrar la implementación más derivada del método definido por el tipo de variable.Esto difiere de Java, que permite "anulaciones implícitas"; por ejemplo, métodos (no estáticos), simplemente definir un método de la misma firma (nombre y número / tipo de parámetros) hará que la subclase anule la superclase.
Debido a que a menudo es útil extender o anular la funcionalidad de un método no virtual que no controla, C # también incluye la
new
palabra clave contextual. Lanew
palabra clave "oculta" el método principal en lugar de anularlo. Se puede ocultar cualquier método heredable, ya sea virtual o no; esto le permite a usted, el desarrollador, aprovechar los miembros que desea heredar de un padre, sin tener que trabajar con los que no, mientras le permite presentar la misma "interfaz" a los consumidores de su código.La ocultación funciona de manera similar a la anulación desde la perspectiva de una persona que usa su objeto en o por debajo del nivel de herencia en el que se define el método de ocultación. A partir del ejemplo de la pregunta, un codificador que crea un Profesor y almacena esa referencia en una variable del tipo Profesor verá el comportamiento de la implementación ShowInfo () del Profesor, que oculta el de Persona. Sin embargo, alguien que trabaje con su objeto en una colección de registros Persona (como usted) verá el comportamiento de la implementación Persona de ShowInfo (); debido a que el método del Profesor no anula a su padre (que también requeriría que Person.ShowInfo () sea virtual), el código que funciona en el nivel de abstracción de la Persona no encontrará la implementación del Maestro y no la usará.
Además, la
new
palabra clave no solo hará esto explícitamente, sino que C # permite ocultar métodos implícitos; simplemente definiendo un método con la misma firma que un método de clase padre, sinoverride
onew
, lo ocultará (aunque producirá una advertencia del compilador o una queja de ciertos asistentes de refactorización como ReSharper o CodeRush). Este es el compromiso que los diseñadores de C # crearon entre las anulaciones explícitas de C ++ frente a las implícitas de Java, y aunque es elegante, no siempre produce el comportamiento que esperarías si vienes de un fondo en cualquiera de los lenguajes más antiguos.Aquí están las novedades: esto se vuelve complejo cuando combina las dos palabras clave en una larga cadena de herencia. Considera lo siguiente:
Salida:
El primer conjunto de cinco es de esperar; Como cada nivel tiene una implementación y se hace referencia a él como un objeto del mismo tipo que se instancia, el tiempo de ejecución resuelve cada llamada al nivel de herencia al que hace referencia el tipo de variable.
El segundo conjunto de cinco es el resultado de asignar cada instancia a una variable del tipo primario inmediato. Ahora, algunas diferencias en el comportamiento se sacuden;
foo2
, que en realidad es unaBar
conversión como aFoo
, todavía encontrará el método más derivado del tipo de objeto real Bar.bar2
es unBaz
, pero a diferencia defoo2
, porque Baz no anula explícitamente la implementación de Bar (no puede; Barsealed
it), no se ve en el tiempo de ejecución cuando se mira "de arriba abajo", por lo que se llama a la implementación de Bar. Tenga en cuenta que Baz no tiene que usar lanew
palabra clave; obtendrá una advertencia del compilador si omite la palabra clave, pero el comportamiento implícito en C # es ocultar el método principal.baz2
es unaBai
, que anulacionesBaz
'snew
implementación,foo2
's; Se llama la implementación del tipo de objeto real en Bai.bai2
es unBat
, que nuevamente ocultaBai
la implementación del método de su padre , y se comporta igual quebar2
aunque la implementación de Bai no esté sellada, por lo que, en teoría, Bat podría haber anulado el método en lugar de haberlo ocultado. Finalmente,bat2
es unBak
, que no tiene una implementación primordial de ningún tipo, y simplemente usa la de su padre.El tercer conjunto de cinco ilustra el comportamiento completo de resolución de arriba hacia abajo. En realidad, todo hace referencia a una instancia de la clase más derivada de la cadena,
Bak
pero la resolución en cada nivel de tipo variable se realiza comenzando en ese nivel de la cadena de herencia y profundizando hasta la anulación explícita más derivada del método, que son los deBar
,Bai
yBat
. El método de ocultación "rompe" la cadena de herencia primordial; tiene que estar trabajando con el objeto en o debajo del nivel de herencia que oculta el método para poder usar el método de ocultación. De lo contrario, el método oculto es "descubierto" y se utiliza en su lugar.fuente
Lea sobre el polimorfismo en C #: Polimorfismo (Guía de programación de C #)
Este es un ejemplo a partir de ahí:
fuente
Debe hacerlo
virtual
y luego anular esa función enTeacher
. Como está heredando y usando el puntero base para referirse a una clase derivada, debe anularlo usandovirtual
.new
es para ocultar elbase
método de clase en una referencia de clase derivada y no en unabase
referencia de clase.fuente
Me gustaría agregar un par de ejemplos más para ampliar la información sobre esto. Espero que esto también ayude:
Aquí hay una muestra de código que limpia el aire alrededor de lo que sucede cuando un tipo derivado se asigna a un tipo base. Qué métodos están disponibles y la diferencia entre los métodos anulados y ocultos en este contexto.
Otra pequeña anomalía es que, para la siguiente línea de código:
El compilador VS (intellisense) mostraría a.foo () como A.foo ().
Por lo tanto, está claro que cuando se asigna un tipo más derivado a un tipo base, la variable 'tipo base' actúa como el tipo base hasta que se hace referencia a un método que se anula en un tipo derivado. Esto puede volverse un poco contra-intuitivo con métodos ocultos o métodos con el mismo nombre (pero no anulados) entre los tipos padre e hijo.
¡Este ejemplo de código debería ayudar a delinear estas advertencias!
fuente
C # es diferente a Java en el comportamiento de anulación de clase padre / hijo. Por defecto, en Java, todos los métodos son virtuales, por lo que el comportamiento que desea se admite de inmediato.
En C #, debe marcar un método como virtual en la clase base, luego obtendrá lo que desea.
fuente
La nueva palabra clave indica que el método en la clase actual solo funcionará si tiene una instancia de la clase Profesor almacenada en una variable de tipo Profesor. O puede activarlo usando castings: ((Profesor) Persona) .ShowInfo ()
fuente
El tipo de variable 'maestro' aquí es
typeof(Person)
y este tipo no sabe nada acerca de la clase Profesor y no trata de buscar ningún método en tipos derivados. Para llamar al método de la clase del profesor que debe emitir su variable de:(person as Teacher).ShowInfo()
.Para llamar a un método específico basado en el tipo de valor, debe usar la palabra clave 'virtual' en su clase base y anular los métodos virtuales en las clases derivadas. Este enfoque permite implementar clases derivadas con o sin anulación de métodos virtuales. Se llamarán métodos de clase base para tipos sin virtuales superpuestos.
fuente
Puede ser demasiado tarde ... Pero la pregunta es simple y la respuesta debe tener el mismo nivel de complejidad.
En su variable de código, la persona no sabe nada sobre Teacher.ShowInfo (). No hay forma de llamar al último método desde la referencia de clase base, porque no es virtual.
Hay un enfoque útil para la herencia: intente imaginar qué quiere decir con su jerarquía de código. También trate de imaginar qué dice una u otra herramienta sobre sí misma. Por ejemplo, si agrega una función virtual a una clase base, suponga: 1. puede tener una implementación predeterminada; 2. podría reimplementarse en clase derivada. Si agrega una función abstracta, solo significa una cosa: la subclase debe crear una implementación. Pero en caso de que tenga una función simple, no espera que nadie cambie su implementación.
fuente
El compilador hace esto porque no sabe que es un
Teacher
. Todo lo que sabe es que es unPerson
o algo derivado de él. Entonces, todo lo que puede hacer es llamar alPerson.ShowInfo()
método.fuente
Solo quería dar una breve respuesta:
Debería usar
virtual
yoverride
en clases que podrían ser anuladas. Úselovirtual
para métodos que pueden ser anulados por clases secundarias yoverride
para métodos que deberían anular dichosvirtual
métodos.fuente
Escribí el mismo código que has mencionado anteriormente en Java, excepto algunos cambios y funcionó bien, salvo los excepciones. El método de la clase base se anula y, por lo tanto, la salida que se muestra es "Soy profesor".
Motivo: Como estamos creando una referencia de la clase base (que es capaz de tener una instancia de referencia de la clase derivada) que en realidad contiene la referencia de la clase derivada. Y como sabemos que la instancia siempre mira primero sus métodos si la encuentra allí, la ejecuta, y si no encuentra la definición allí, sube en la jerarquía.
fuente
Sobre la base de la excelente demostración de Keith S. y las respuestas de calidad de todos los demás, y en aras de la completa integridad, sigamos adelante y agreguemos implementaciones de interfaz explícitas para demostrar cómo funciona. Considere lo siguiente:
espacio de nombres LinqConsoleApp {
}
Aquí está la salida:
persona: Soy persona == LinqConsoleApp.Teacher
profesor: Soy profesor == LinqConsoleApp.Teacher
persona1: Soy profesor == LinqConsoleApp.Teacher
persona2: Soy profesor == LinqConsoleApp.Teacher
profesor1: Soy profesor == LinqConsoleApp.Teacher
persona4: Soy persona == LinqConsoleApp.Person
person3: Soy interfaz Person == LinqConsoleApp.Person
Dos cosas a tener en cuenta:
El método Teacher.ShowInfo () omite la nueva palabra clave. Cuando se omite nuevo, el comportamiento del método es el mismo que si la nueva palabra clave se definiera explícitamente.
Solo puede usar la palabra clave de anulación junto con la palabra clave virtual. El método de la clase base debe ser virtual. O abstracto en cuyo caso la clase también debe ser abstracta.
person obtiene la implementación base de ShowInfo porque la clase Teacher no puede anular la implementación base (sin declaración virtual) y person es .GetType (Teacher), por lo que oculta la implementación de la clase Teacher.
profesor obtiene la implementación derivada de profesor de ShowInfo porque profesor porque es Typeof (Profesor) y no está en el nivel de herencia de Persona.
person1 obtiene la implementación derivada del Profesor porque es .GetType (Profesor) y la nueva palabra clave implícita oculta la implementación base.
person2 también obtiene la implementación derivada del Profesor a pesar de que implementa IPerson y obtiene un reparto explícito a IPerson. Esto se debe nuevamente a que la clase Profesor no implementa explícitamente el método IPerson.ShowInfo ().
teacher1 también obtiene la implementación derivada de Teacher porque es .GetType (Teacher).
Solo person3 obtiene la implementación IPerson de ShowInfo porque solo la clase Person implementa explícitamente el método y person3 es una instancia del tipo IPerson.
Para implementar explícitamente una interfaz, debe declarar una instancia var del tipo de interfaz de destino y una clase debe implementar explícitamente (calificar completamente) los miembros de la interfaz.
Observe que ni siquiera person4 obtiene la implementación IPerson.ShowInfo. Esto se debe a que aunque person4 es .GetType (Person) y aunque Person implementa IPerson, person4 no es una instancia de IPerson.
fuente
Muestra de LinQPad para iniciar ciegamente y reducir la duplicación de código. Creo que es lo que estaba tratando de hacer.
fuente