Por que chamar um método na minha classe derivada chama o método da classe base?


146

Considere 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");
    }
}

Quando executo esse código, é produzido o seguinte:

Eu sou pessoa

No entanto, você pode ver que é uma instância de Teacher, não de Person. Por que o código faz isso?


3
Pergunta de uma pessoa Java: is the Console.ReadLine (); necessário para este exemplo?
Rich

2
@ Shahrooz Não consigo responder à sua pergunta - não sei C #. Eu estava fazendo uma pergunta C # muito trivial, que é se a chamada para ReadLine no método principal é necessária para poder chamar WriteLine nas classes Person e Teacher.
Rich

6
Sim, o .Net fecha automaticamente a janela do console quando Main () sai. Para contornar isso, usamos Console.Read () ou Console.Readline () para aguardar uma entrada adicional para que o console permaneça aberto.
Capitão Kenpachi

15
@ Rich não, não é necessário , mas muitas vezes você o verá por esse motivo: ao executar um programa de console do Visual Studio, no encerramento do programa, a janela de comando se fecha imediatamente; portanto, se você deseja ver a saída do programa, é necessário informar esperar.
AakashM

1
@AakashM Obrigado - passo meu tempo no Eclipse, onde o console faz parte da janela do Eclipse e, portanto, não fecha. Isso faz todo o sentido.
18713 Rich

Respostas:


368

Há uma diferença entre newe virtual/ override.

Você pode imaginar que uma classe, quando instanciada, nada mais é do que uma tabela de ponteiros, apontando para a implementação real de seus métodos. A imagem a seguir deve visualizar isso muito bem:

Ilustração de implementações de métodos

Agora, existem diferentes maneiras de definir um método. Cada um se comporta diferente quando é usado com herança. A maneira padrão sempre funciona como a imagem acima ilustra. Se você deseja alterar esse comportamento, pode anexar palavras-chave diferentes ao seu método.

1. Classes abstratas

O primeiro é abstract. abstractOs métodos simplesmente apontam para lugar nenhum:

Ilustração de aulas abstratas

Se sua classe contiver membros abstratos, ela também precisará ser marcada como abstract, caso contrário, o compilador não compilará seu aplicativo. Você não pode criar instâncias de abstractclasses, mas pode herdar delas e criar instâncias de suas classes herdadas e acessá-las usando a definição de classe base. No seu exemplo, isso seria semelhante a:

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!");
    }
}

Se chamado, o comportamento de ShowInfovaria, com base na implementação:

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

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

Ambos, Students e Teachers são Persons, mas se comportam de maneira diferente quando solicitados a solicitar informações sobre si mesmos. No entanto, a maneira de solicitar que eles solicitem suas informações é a mesma: usando a Personinterface da classe.

Então, o que acontece nos bastidores, quando você herda Person? Ao implementar ShowInfo, o ponteiro não está mais apontando para lugar nenhum, agora aponta para a implementação real! Ao criar uma Studentinstância, ele aponta para Students ShowInfo:

Ilustração de métodos herdados

2. Métodos virtuais

A segunda maneira é usar virtualmétodos. O comportamento é o mesmo, exceto que você está fornecendo uma implementação padrão opcional em sua classe base. Classes com virtualmembros podem ser instanciadas, no entanto, classes herdadas podem fornecer implementações diferentes. Aqui está como seu código deve realmente 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!");
    }
}

A principal diferença é que o membro base Person.ShowInfonão está mais apontando para lugar nenhum. Esse também é o motivo pelo qual você pode criar instâncias de Person(e, portanto, não precisa mais ser marcado como abstract):

Ilustração de um membro virtual dentro de uma classe base

Você deve notar que isso não parece diferente da primeira imagem no momento. Isso ocorre porque o virtualmétodo está apontando para uma implementação " da maneira padrão ". Usando virtual, você pode dizer Persons, que eles podem (não devem ) fornecer uma implementação diferente para ShowInfo. Se você fornecer uma implementação diferente (usando override), como eu fiz para o Teacheracima, a imagem terá a mesma aparência de abstract. Imagine, não fornecemos uma implementação personalizada para Students:

public class Student : Person
{
}

O código seria chamado assim:

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

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

E a imagem para Studentficaria assim:

Ilustração da implementação padrão de um método, usando a palavra-chave virtual

3. A palavra-chave mágica `new`, conhecida como" Shadowing "

newé mais um truque em torno disso. Você pode fornecer métodos em classes generalizadas, com os mesmos nomes que os métodos na classe / interface base. Ambos apontam para sua própria implementação personalizada:

Ilustração da "maneira de contornar" usando a nova palavra-chave

A implementação se parece com a que você forneceu. O comportamento é diferente, com base na maneira como você acessa o método:

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

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

Esse comportamento pode ser desejado, mas no seu caso, é enganoso.

Espero que isso torne as coisas mais claras para você entender!


9
Obrigado pela sua ótima resposta

6
O que você usou para gerar esses diagramas?
BlueRaja - Danny Pflughoeft

2
Resposta excelente e muito completa.
Nik Bougalis

8
tl; dr você usou newque breaks herança da função e faz com que a nova função de separar da superclasse função
aberração catraca

3
@Taymon: Na verdade não ... Eu só queria deixar claro que a chamada agora vai contra Person, não Student;)
Carsten

45

O polimorfismo de subtipo em C # usa virtualidade explícita, semelhante ao C ++, mas diferente do Java. Isso significa que você precisa explicitamente marcar métodos como substituíveis (ou seja virtual). No C #, você também precisa marcar explicitamente os métodos de substituição como substituindo (ou seja override) para evitar erros de digitação.

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");
    }
}

No código da sua pergunta, você usa o newque faz sombreamento em vez de substituir. O sombreamento afeta apenas a semântica em tempo de compilação em vez da semântica de tempo de execução, daí a saída não intencional.


4
Quem dirá que o OP sabe o que isso significa.
Cole Johnson

@ColeJohnson adicionarei um esclarecimento.

25

Você tem que fazer o método virtual e substituir a função na classe filho, a fim de chamar o método do objeto de classe que você coloca na referência da classe pai.

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 virtuais

Quando um método virtual é chamado, o tipo de tempo de execução do objeto é verificado para um membro substituinte. O membro substituinte na classe mais derivada é chamado, que pode ser o membro original, se nenhuma classe derivada tiver substituído o membro. Por padrão, os métodos não são virtuais. Você não pode substituir um método não virtual. Você não pode usar o modificador virtual com os modificadores estático, abstrato, privado ou de substituição, MSDN .

Usando novo para sombreamento

Você está usando uma nova palavra-chave em vez de substituir, é isso que as novas

  • Se o método na classe derivada não for precedido por palavras-chave novas ou de substituição, o compilador emitirá um aviso e o método se comportará como se a nova palavra-chave estivesse presente.

  • Se o método na classe derivada for precedido com a nova palavra-chave, o método será definido como independente do método da classe base , este artigo do MSDN explica muito bem.

Ligação antecipada VS Ligação tardia

Temos ligação antecipada em tempo de compilação para o método normal (não virtual), que é o caso atual em que o compilador ligará a chamada para o método da classe base que é o método do tipo de referência (classe base) em vez do objeto ser mantido na referência da base classe ou seja, objeto de classe derivada . Isso ocorre porque ShowInfonão é um método virtual. A ligação tardia é executada em tempo de execução para (método virtual / substituído) usando a tabela de método virtual (vtable).

Para uma função normal, o compilador pode determinar a localização numérica dele na memória. Então, quando a função é chamada, pode gerar uma instrução para chamar a função neste endereço.

Para um objeto que possui métodos virtuais, o compilador gerará uma tabela em V. Essa é essencialmente uma matriz que contém os endereços dos métodos virtuais. Todo objeto que possui um método virtual conterá um membro oculto gerado pelo compilador que é o endereço da tabela v. Quando uma função virtual é chamada, o compilador descobrirá qual é a posição do método apropriado na tabela v. Ele irá gerar código para procurar na tabela v de objetos e chamar o método virtual nesta posição, Reference .


7

Eu quero aproveitar a resposta de Achratt . Para completar, a diferença é que o OP espera que a newpalavra - chave no método da classe derivada substitua o método da classe base. O que ele realmente faz é ocultar o método da classe base.

Em C #, como outra resposta mencionada, a substituição do método tradicional deve ser explícita; o método da classe base deve ser marcado como virtuale a classe derivada deve especificamenteoverride o método da classe base. Se isso for feito, não importa se o objeto é tratado como uma instância da classe base ou da classe derivada; o método derivado é encontrado e chamado. Isso é feito de maneira semelhante à do C ++; um método marcado como "virtual" ou "substituir", quando compilado, é resolvido como "atrasado" (em tempo de execução), determinando o tipo real do objeto referenciado e percorrendo a hierarquia do objeto para baixo ao longo da árvore do tipo de variável para o tipo de objeto real, para encontrar a implementação mais derivada do método definido pelo tipo de variável.

Isso difere do Java, que permite "substituições implícitas"; por exemplo, métodos (não estáticos), simplesmente definir um método da mesma assinatura (nome e número / tipo de parâmetros) fará com que a subclasse substitua a superclasse.

Como geralmente é útil estender ou substituir a funcionalidade de um método não virtual que você não controla, o C # também inclui a newpalavra-chave contextual. A newpalavra-chave "oculta" o método pai em vez de substituí-lo. Qualquer método herdável pode ser oculto, seja virtual ou não; isso permite que você, desenvolvedor, aproveite os membros que deseja herdar de um pai, sem ter que contornar os que não são, enquanto ainda permite apresentar a mesma "interface" aos consumidores do seu código.

Ocultar funciona de maneira semelhante à substituição da perspectiva de uma pessoa que usa seu objeto no nível de herança ou abaixo do nível em que o método de ocultação está definido. Do exemplo da pergunta, um codificador que cria um professor e armazena essa referência em uma variável do tipo professor verá o comportamento da implementação ShowInfo () do professor, que oculta o de pessoa. No entanto, alguém que trabalha com seu objeto em uma coleção de registros de Pessoa (como você é) verá o comportamento da implementação de ShowInfo (); como o método do professor não substitui seu pai (o que também exigiria que Person.ShowInfo () seja virtual), o código que funciona no nível de abstração da pessoa não encontrará a implementação do professor e não a usará.

Além disso, a newpalavra - chave não apenas fará isso explicitamente, como também o C # permite ocultar o método implícito; simplesmente definir um método com a mesma assinatura que um método de classe pai, sem overrideou new, irá ocultá-lo (embora produza um aviso do compilador ou uma reclamação de determinados assistentes de refatoração, como ReSharper ou CodeRush). Esse é o compromisso que os designers do C # criaram entre as substituições explícitas do C ++ e as implícitas do Java e, embora seja elegante, nem sempre produz o comportamento que você esperaria se tivesse experiência em um dos idiomas mais antigos.

Aqui estão as novidades: isso fica complexo quando você combina as duas palavras-chave em uma longa cadeia de herança. Considere o seguinte:

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

Resultado:

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

O primeiro conjunto de cinco é de se esperar; como cada nível possui uma implementação e é referenciado como um objeto do mesmo tipo que foi instanciado, o tempo de execução resolve cada chamada para o nível de herança referenciado pelo tipo de variável.

O segundo conjunto de cinco é o resultado da atribuição de cada instância a uma variável do tipo pai imediato. Agora, algumas diferenças de comportamento se agitam; foo2, que na verdade é um Barelenco como umFoo , ainda encontrará o método mais derivado do tipo de objeto real Bar. bar2é um Baz, mas diferente de foo2, porque o Baz não substitui explicitamente a implementação do Bar (não pode; Bar sealedit), ele não é visto pelo tempo de execução ao olhar "de cima para baixo"; portanto, a implementação do Bar é chamada. Observe que o Baz não precisa usar a newpalavra - chave; você receberá um aviso do compilador se omitir a palavra-chave, mas o comportamento implícito no C # é ocultar o método pai. baz2é umBai , que substitui Bazonewimplementação, portanto seu comportamento é semelhante aoé a , que não possui implementação de nenhum tipo e simplesmente usa a de seu pai.foo2's; a implementação do tipo de objeto real em Bai é chamada. , que novamente oculta a implementação do método pai e se comporta da mesma maneira que a implementação de Bai não está selada, então, teoricamente, Bat poderia ter substituído, em vez de ocultado o método. Finalmente, é umbai2BatBaibar2bat2Bak

O terceiro conjunto de cinco ilustra o comportamento completo da resolução de cima para baixo. Na verdade, tudo está fazendo referência a uma instância da classe mais derivada da cadeia, Bakmas a resolução em todos os níveis do tipo de variável é executada iniciando nesse nível da cadeia de herança e detalhando a substituição explícita mais derivada do método, que é aqueles em Bar, Bai, e Bat. O método oculto "quebra" a cadeia de herança primordial; você deve estar trabalhando com o objeto no nível de herança ou abaixo dele que oculta o método para que o método oculto seja usado. Caso contrário, o método oculto é "descoberto" e usado.


4

Leia sobre o polimorfismo em C #: Polimorfismo (Guia de Programação em C #)

Este é um exemplo de lá:

Quando a nova palavra-chave é usada, os novos membros da classe são chamados em vez dos membros da classe base que foram substituídos. Esses membros da classe base são chamados de membros ocultos. Os membros da classe oculta ainda podem ser chamados se uma instância da classe derivada for convertida para uma instância da classe base. Por exemplo:

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

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

3

Você precisa fazer isso virtuale substituir essa função em Teacher. Como você está herdando e usando o ponteiro base para se referir a uma classe derivada, é necessário substituí-lo usando virtual. newé para ocultar o basemétodo de classe em uma referência de classe derivada e não em uma basereferência de classe.


3

Gostaria de adicionar mais alguns exemplos para expandir as informações em torno disso. Espero que isso ajude também:

Aqui está um exemplo de código que limpa o ar em torno do que acontece quando um tipo derivado é atribuído a um tipo de base. Quais métodos estão disponíveis e a diferença entre métodos substituídos e ocultos nesse 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()");
        }
    }
}

Outra pequena anomalia é que, para a seguinte linha de código:

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

O compilador VS (intellisense) mostraria a.foo () como A.foo ().

Portanto, fica claro que, quando um tipo mais derivado é atribuído a um tipo de base, a variável 'tipo de base' atua como o tipo de base até que um método que seja substituído em um tipo derivado seja referenciado. Isso pode se tornar um pouco contra-intuitivo com métodos ocultos ou métodos com o mesmo nome (mas não substituídos) entre os tipos pai e filho.

Este exemplo de código deve ajudar a delinear essas advertências!


2

C # é diferente de java no comportamento de substituição de classe pai / filho. Por padrão, em Java, todos os métodos são virtuais, portanto, o comportamento que você deseja é suportado imediatamente.

No C #, você precisa marcar um método como virtual na classe base, para obter o que deseja.


2

A nova palavra-chave informa que o método na classe atual só funcionará se você tiver uma instância da classe Teacher armazenada em uma variável do tipo Teacher. Ou você pode ativá-lo usando castings: ((Teacher) Person) .ShowInfo ()


1

O tipo de variável 'professor' aqui é typeof(Person)e esse tipo não sabe nada sobre a classe Professor e não tenta procurar nenhum método nos tipos derivados. Para chamar o método da classe Teacher, você deve converter sua variável:(person as Teacher).ShowInfo() .

Para chamar um método específico com base no tipo de valor, você deve usar a palavra-chave 'virtual' em sua classe base e substituir métodos virtuais em classes derivadas. Essa abordagem permite implementar classes derivadas com ou sem substituição de métodos virtuais. Os métodos da classe base serão chamados para tipos sem virtuais sobrecarregados.

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
{

}

1

Pode ser tarde demais ... Mas a pergunta é simples e a resposta deve ter o mesmo nível de complexidade.

No seu código, a variável person não sabe nada sobre Teacher.ShowInfo (). Não há como chamar o último método a partir da referência da classe base, porque não é virtual.

Existe uma abordagem útil para a herança - tente imaginar o que você quer dizer com sua hierarquia de códigos. Tente também imaginar o que uma ou outra ferramenta diz sobre si mesma. Por exemplo, se você adicionar uma função virtual a uma classe base, supõe: 1. ela pode ter implementação padrão; 2. pode ser reimplementado na classe derivada. Se você adicionar uma função abstrata, isso significa apenas uma coisa - a subclasse deve criar uma implementação. Mas caso você tenha uma função simples - você não espera que ninguém mude sua implementação.


0

O compilador faz isso porque não sabe que é a Teacher. Tudo o que sabe é que é um Personou algo derivado disso. Então, tudo o que você pode fazer é chamar o Person.ShowInfo()método


0

Só queria dar uma breve resposta -

Você deve usar virtuale overrideem classes que possam ser substituídas. Use virtualpara métodos que podem ser substituídos por classes filho e use overridepara métodos que devem substituir esses virtualmétodos.


0

Eu escrevi o mesmo código que você mencionou acima em java, exceto algumas alterações e funcionou bem como exceção. O método da classe base é substituído e, portanto, a saída exibida é "Eu sou professor".

Motivo: como estamos criando uma referência da classe base (que é capaz de ter instância de referência da classe derivada) que na verdade contém a referência da classe derivada. E como sabemos que a instância sempre olha seus métodos primeiro, se a encontrar lá, a executa e, se não encontrar a definição, subirá na hierarquia.

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");
    }
}

0

Com base na excelente demonstração de Keith S. e nas respostas de qualidade de todos os demais, e por uma questão de super uber, vamos em frente e lançar implementações explícitas de interface para demonstrar como isso funciona. Considere o seguinte:

namespace 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());
    }
}

}

Aqui está a saída:

pessoa: Eu sou Pessoa == LinqConsoleApp.Teacher

professor: Eu sou professor == LinqConsoleApp.Teacher

person1: Sou professor == LinqConsoleApp.Teacher

person2: Sou professor == LinqConsoleApp.Teacher

teacher1: Sou professor == LinqConsoleApp.Teacher

person4: Eu sou Person == LinqConsoleApp.Person

person3: Sou interface Person == LinqConsoleApp.Person

Duas coisas a serem observadas:
O método Teacher.ShowInfo () omite a nova palavra-chave. Quando new é omitido, o comportamento do método é o mesmo que se a nova palavra-chave tivesse sido explicitamente definida.

Você só pode usar a palavra-chave override em conjunto com a palavra-chave virtual. O método da classe base deve ser virtual. Ou abstrato, nesse caso a classe também deve ser abstrata.

person obtém a implementação base do ShowInfo porque a classe Teacher não pode substituir a implementação base (sem declaração virtual) e person é .GetType (Teacher), portanto oculta a implementação da classe Teacher.

O professor obtém a implementação derivada do professor de ShowInfo porque o professor é Typeof (Professor) e não está no nível de herança da Pessoa.

person1 obtém a implementação derivada do professor porque é .GetType (professor) e a nova palavra-chave implícita oculta a implementação básica.

person2 também obtém a implementação derivada do professor, embora implemente IPerson e obtém uma conversão explícita para IPerson. Isso ocorre novamente porque a classe Teacher não implementa explicitamente o método IPerson.ShowInfo ().

teacher1 também obtém a implementação derivada do professor porque é .GetType (Teacher).

Somente person3 obtém a implementação IPerson de ShowInfo porque apenas a classe Person implementa explicitamente o método e person3 é uma instância do tipo IPerson.

Para implementar explicitamente uma interface, você deve declarar uma instância var do tipo de interface de destino e uma classe deve implementar explicitamente (qualificar totalmente) os membros da interface.

Observe que nem person4 obtém a implementação IPerson.ShowInfo. Isso ocorre porque, embora person4 seja .GetType (Person) e mesmo que Person implemente IPerson, person4 não é uma instância do IPerson.


Vejo que a formatação do código corretamente está apresentando um desafio. Não há tempo para resolvê-lo agora ...
steely

0

Exemplo do LinQPad para iniciar às cegas e reduzir a duplicação de código. Acho que é isso que você estava tentando fazer.

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();
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.