¿Por qué llamar a un método en mi clase derivada llama al método de la clase base?

146

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?

azulado
fuente
3
Pregunta de una persona Java: es Console.ReadLine (); necesario para este ejemplo?
Rico
2
@Shahrooz No puedo responder tu pregunta, no sé C #. Estaba haciendo una pregunta trivial en C #, que es si la llamada a ReadLine en el método principal es necesaria para poder llamar a WriteLine en las clases de Persona y Profesor.
Rico
66
Sí, .Net cierra automáticamente la ventana de la consola cuando sale Main (). para solucionar esto, usamos Console.Read () o Console.Readline () para esperar entradas adicionales para que la consola permanezca abierta.
Capitán Kenpachi
15
@Rich no, no es necesario , pero a menudo lo verá por este motivo: al ejecutar un programa de consola desde Visual Studio, al finalizar el programa, la ventana de comandos se cierra de inmediato, por lo que si desea ver la salida del programa, debe informarle esperar.
AakashM
1
@AakashM Gracias: paso mi tiempo en Eclipse, donde la consola es parte de la ventana de Eclipse, por lo que no se cierra. Eso tiene mucho sentido.
Rico

Respuestas:

368

Hay una diferencia entre newy virtual/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:

Ilustración de implementaciones de métodos

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. abstractlos métodos simplemente apuntan a ninguna parte:

Ilustración de clases abstractas

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 de abstractclases, 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í:

public abstract class Person
{
    public abstract void ShowInfo();
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a teacher!");
    }
}

public class Student : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a student!");
    }
}

Si se llama, el comportamiento de ShowInfovaría, en función de la implementación:

Person person = new Teacher();
person.ShowInfo();    // Shows 'I am a teacher!'

person = new Student();
person.ShowInfo();    // Shows 'I am a student!'

Ambos, Students y Teachers son Persons, 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 la Personinterfaz de clase.

Entonces, ¿qué sucede detrás de escena, cuando heredas de Person? Al implementar ShowInfo, el puntero ya no apunta a ninguna parte , ¡ahora apunta a la implementación real! Al crear una Studentinstancia, apunta a Students ShowInfo:

Ilustración de métodos heredados

2. Métodos virtuales

La segunda forma es usar virtualmétodos. El comportamiento es el mismo, excepto que proporciona una implementación predeterminada opcional en su clase base. Las clases con virtualmiembros pueden instanciarse, sin embargo, las clases heredadas pueden proporcionar implementaciones diferentes. Así es como debería verse realmente su código para funcionar:

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am a person!");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a teacher!");
    }
}

La diferencia clave es que el miembro base Person.ShowInfoya no apunta a ninguna parte . Esta es también la razón por la que puede crear instancias de Person(y, por lo tanto, no es necesario que se marque abstractmás):

Ilustración de un miembro virtual dentro de una clase base

Debes notar que esto no se ve diferente de la primera imagen por ahora. Esto se debe a que el virtualmétodo apunta a una implementación "de la manera estándar ". Usando virtual, puede decir Persons, que pueden (no deben ) proporcionar una implementación diferente para ShowInfo. Si proporciona una implementación diferente (usando override), como lo hice para lo Teacheranterior, la imagen se vería igual que para abstract. Imagínese, no proporcionamos una implementación personalizada para Students:

public class Student : Person
{
}

El código se llamaría así:

Person person = new Teacher();
person.ShowInfo();    // Shows 'I am a teacher!'

person = new Student();
person.ShowInfo();    // Shows 'I am a person!'

Y la imagen para Studentse vería así:

Ilustración de la implementación predeterminada de un método, usando virtual-keyword

3. La palabra clave mágica `new` también conocida como" Sombrear "

newEs 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:

Ilustración del "camino" usando la nueva palabra clave

La implementación se parece a la que usted proporcionó. El comportamiento difiere según la forma en que accede al método:

Teacher teacher = new Teacher();
Person person = (Person)teacher;

teacher.ShowInfo();    // Prints 'I am a teacher!'
person.ShowInfo();     // Prints 'I am a person!'

Este comportamiento puede ser deseado, pero en su caso es engañoso.

¡Espero que esto te aclare las cosas!

Carsten
fuente
9
Gracias por su gran respuesta
66
¿Qué usaste para generar esos diagramas?
BlueRaja - Danny Pflughoeft
2
Excelente y muy completa respuesta.
Nik Bougalis
8
tl; dr que utilizó newel que se rompe la herencia de la función y hace que la nueva función separada de la superclase función
monstruo de trinquete
3
@Taymon: En realidad no ... solo quería aclarar que la llamada ahora va en contra Person, no Student;)
Carsten
45

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 decir override) para evitar errores tipográficos.

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

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.

Comunidad
fuente
44
Quién puede decir que el OP sabe lo que significan.
Cole Johnson
@ColeJohnson Agregaré una aclaración.
25

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.

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}
public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

Métodos virtuales

Cuando se invoca un método virtual, se verifica el tipo de tiempo de ejecución del objeto para un miembro superior. Se llama al miembro superior en la clase más derivada, que podría ser el miembro original, si ninguna clase derivada ha anulado al miembro. Por defecto, los métodos no son virtuales. No puede anular un método no virtual. No puede usar el modificador virtual con los modificadores estáticos, abstractos, privados o de anulación, MSDN .

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 ShowInfoque 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).

Para una función normal, el compilador puede calcular su ubicación numérica en la memoria. Luego, cuando se llama a la función, puede generar una instrucción para llamar a la función en esta dirección.

Para un objeto que tiene métodos virtuales, el compilador generará una v-table. Esto es esencialmente una matriz que contiene las direcciones de los métodos virtuales. Cada objeto que tiene un método virtual contendrá un miembro oculto generado por el compilador que es la dirección de la v-table. Cuando se llama a una función virtual, el compilador determinará cuál es la posición del método apropiado en la tabla v. Luego generará código para buscar en la tabla v de objetos y llamar al método virtual en esta posición, Referencia .

Adil
fuente
7

Quiero construir a partir de la respuesta de Achratt . Para completar, la diferencia es que el OP está esperando elnew 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 comovirtual y la clase derivada debe ser específicamente overrideel 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 newpalabra clave contextual. La newpalabra 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 newpalabra 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, sin overrideo new, 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:

class Foo { public virtual void DoFoo() { Console.WriteLine("Foo"); } }
class Bar:Foo { public override sealed void DoFoo() { Console.WriteLine("Bar"); } }
class Baz:Bar { public virtual void DoFoo() { Console.WriteLine("Baz"); } }
class Bai:Baz { public override void DoFoo() { Console.WriteLine("Bai"); } }
class Bat:Bai { public new void DoFoo() { Console.WriteLine("Bat"); } }
class Bak:Bat { }

Foo foo = new Foo();
Bar bar = new Bar();
Baz baz = new Baz();
Bai bai = new Bai();
Bat bat = new Bat();

foo.DoFoo();
bar.DoFoo();
baz.DoFoo();
bai.DoFoo();
bat.DoFoo();

Console.WriteLine("---");

Foo foo2 = bar;
Bar bar2 = baz;
Baz baz2 = bai;
Bai bai2 = bat;
Bat bat2 = new Bak();

foo2.DoFoo();
bar2.DoFoo();
baz2.DoFoo();
bai2.DoFoo();    

Console.WriteLine("---");

Foo foo3 = bak;
Bar bar3 = bak;
Baz baz3 = bak;
Bai bai3 = bak;
Bat bat3 = bak;

foo3.DoFoo();
bar3.DoFoo();
baz3.DoFoo();
bai3.DoFoo();    
bat3.DoFoo();

Salida:

Foo
Bar
Baz
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat

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 una Barconversión como a Foo, todavía encontrará el método más derivado del tipo de objeto real Bar. bar2es un Baz, pero a diferencia de foo2, porque Baz no anula explícitamente la implementación de Bar (no puede; Bar sealedit), 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 la newpalabra clave; obtendrá una advertencia del compilador si omite la palabra clave, pero el comportamiento implícito en C # es ocultar el método principal. baz2es una Bai, que anulaciones Baz'snew implementación,foo2 's; Se llama la implementación del tipo de objeto real en Bai. bai2es un Bat, que nuevamente oculta Baila implementación del método de su padre , y se comporta igual que bar2aunque 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, bat2es un Bak, 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, Bakpero 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 de Bar, Baiy Bat. 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.

KeithS
fuente
4

Lea sobre el polimorfismo en C #: Polimorfismo (Guía de programación de C #)

Este es un ejemplo a partir de ahí:

Cuando se utiliza la nueva palabra clave, se llama a los nuevos miembros de la clase en lugar de los miembros de la clase base que se han reemplazado. Esos miembros de la clase base se llaman miembros ocultos. Los miembros de clase ocultos aún se pueden llamar si una instancia de la clase derivada se convierte en una instancia de la clase base. Por ejemplo:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.
Emil
fuente
3

Debe hacerlo virtualy luego anular esa función en Teacher. Como está heredando y usando el puntero base para referirse a una clase derivada, debe anularlo usando virtual. newes para ocultar el basemétodo de clase en una referencia de clase derivada y no en una basereferencia de clase.

Jay Patel
fuente
3

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.

namespace TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.foo();        // A.foo()
            a.foo2();       // A.foo2()

            a = new B();    
            a.foo();        // B.foo()
            a.foo2();       // A.foo2()
            //a.novel() is not available here

            a = new C();
            a.foo();        // C.foo()
            a.foo2();       // A.foo2()

            B b1 = (B)a;    
            b1.foo();       // C.foo()
            b1.foo2();      // B.foo2()
            b1.novel();     // B.novel()

            Console.ReadLine();
        }
    }


    class A
    {
        public virtual void foo()
        {
            Console.WriteLine("A.foo()");
        }

        public void foo2()
        {
            Console.WriteLine("A.foo2()");
        }
    }

    class B : A
    {
        public override void foo()
        {
            // This is an override
            Console.WriteLine("B.foo()");
        }

        public new void foo2()      // Using the 'new' keyword doesn't make a difference
        {
            Console.WriteLine("B.foo2()");
        }

        public void novel()
        {
            Console.WriteLine("B.novel()");
        }
    }

    class C : B
    {
        public override void foo()
        {
            Console.WriteLine("C.foo()");
        }

        public new void foo2()
        {
            Console.WriteLine("C.foo2()");
        }
    }
}

Otra pequeña anomalía es que, para la siguiente línea de código:

A a = new B();    
a.foo(); 

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!

Vaibhav
fuente
2

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.

Adrian Salazar
fuente
2

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 ()

Miguel
fuente
1

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.

public class Program
{
    private static void Main(string[] args)
    {
        Person teacher = new Teacher();
        teacher.ShowInfo();

        Person incognito = new IncognitoPerson ();
        incognito.ShowInfo();

        Console.ReadLine();
    }
}

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

public class IncognitoPerson : Person
{

}
Vi Ki
fuente
1

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.

Illia Levandovskyi
fuente
0

El compilador hace esto porque no sabe que es un Teacher. Todo lo que sabe es que es un Persono algo derivado de él. Entonces, todo lo que puede hacer es llamar al Person.ShowInfo()método.

Cole Johnson
fuente
0

Solo quería dar una breve respuesta:

Debería usar virtualy overrideen clases que podrían ser anuladas. Úselo virtualpara métodos que pueden ser anulados por clases secundarias y overridepara métodos que deberían anular dichos virtualmétodos.

Shruti Kapoor
fuente
0

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.

public class inheritance{

    public static void main(String[] args){

        Person person = new Teacher();
        person.ShowInfo();
    }
}

class Person{

    public void ShowInfo(){
        System.out.println("I am Person");
    }
}

class Teacher extends Person{

    public void ShowInfo(){
        System.out.println("I am Teacher");
    }
}
Sanjeev Chauhan
fuente
0

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 {

class Program
{

    static void Main(string[] args)
    {


        Person person = new Teacher();
        Console.Write(GetMemberName(() => person) + ": ");
        person.ShowInfo();

        Teacher teacher = new Teacher();
        Console.Write(GetMemberName(() => teacher) + ": ");
        teacher.ShowInfo();

        IPerson person1 = new Teacher();
        Console.Write(GetMemberName(() => person1) + ": ");
        person1.ShowInfo();

        IPerson person2 = (IPerson)teacher;
        Console.Write(GetMemberName(() => person2) + ": ");
        person2.ShowInfo();

        Teacher teacher1 = (Teacher)person1;
        Console.Write(GetMemberName(() => teacher1) + ": ");
        teacher1.ShowInfo();

        Person person4 = new Person();
        Console.Write(GetMemberName(() => person4) + ": ");
        person4.ShowInfo();

        IPerson person3 = new Person();
        Console.Write(GetMemberName(() => person3) + ": ");
        person3.ShowInfo();

        Console.WriteLine();

        Console.ReadLine();

    }

    private static string GetMemberName<T>(Expression<Func<T>> memberExpression)
    {
        MemberExpression expressionBody = (MemberExpression)memberExpression.Body;
        return expressionBody.Member.Name;
    }

}
interface IPerson
{
    void ShowInfo();
}
public class Person : IPerson
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Person == " + this.GetType());
    }
    void IPerson.ShowInfo()
    {
        Console.WriteLine("I am interface Person == " + this.GetType());
    }
}
public class Teacher : Person, IPerson
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Teacher == " + this.GetType());
    }
}

}

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.

acerado
fuente
Veo que formatear el código correctamente presenta un desafío. No hay tiempo para bastante hasta ahora ...
acerada
0

Muestra de LinQPad para iniciar ciegamente y reducir la duplicación de código. Creo que es lo que estaba tratando de hacer.

void Main()
{
    IEngineAction Test1 = new Test1Action();
    IEngineAction Test2 = new Test2Action();
    Test1.Execute("Test1");
    Test2.Execute("Test2");
}

public interface IEngineAction
{
    void Execute(string Parameter);
}

public abstract class EngineAction : IEngineAction
{
    protected abstract void PerformAction();
    protected string ForChildren;
    public void Execute(string Parameter)
    {  // Pretend this method encapsulates a 
       // lot of code you don't want to duplicate 
      ForChildren = Parameter;
      PerformAction();
    }
}

public class Test1Action : EngineAction
{
    protected override void PerformAction()
    {
        ("Performed: " + ForChildren).Dump();
    }
}

public class Test2Action : EngineAction
{
    protected override void PerformAction()
    {
        ("Actioned: " + ForChildren).Dump();
    }
}
Yrd
fuente