Perché chiamare un metodo nella mia classe derivata chiama il metodo della classe base?


146

Considera questo codice:

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 eseguo questo codice, viene emesso quanto segue:

Io sono persona

Tuttavia, puoi vedere che si tratta di un'istanza di Teacher, non di Person. Perché il codice lo fa?


3
Domanda di una persona Java: è Console.ReadLine (); necessario per questo esempio?
Rich

2
@Shahrooz Non posso rispondere alla tua domanda - Non conosco C #. Stavo ponendo una domanda C # molto banale, ovvero se la chiamata a ReadLine nel metodo principale è necessaria per poter chiamare WriteLine nelle classi Person e Teacher.
Rich

6
Sì, .Net chiude automaticamente la finestra della console all'uscita di Main (). per ovviare a questo, usiamo Console.Read () o Console.Readline () per attendere ulteriori input in modo che la console rimanga aperta.
Capitano Kenpachi,

15
@Rich no, non è necessario , ma lo vedrai spesso per questo motivo: quando esegui un programma console da Visual Studio, al termine del programma la finestra di comando si chiude immediatamente, quindi se vuoi vedere l' output del programma devi dirlo aspettare.
AakashM,

1
@AakashM Grazie - Passo il mio tempo in Eclipse dove la console fa parte della finestra Eclipse e quindi non si chiude. Questo ha perfettamente senso.
Rich

Risposte:


368

C'è una differenza tra newe virtual/ override.

Potete immaginare che una classe, quando istanziata, non è altro che una tabella di puntatori, che indica l'implementazione effettiva dei suoi metodi. L'immagine seguente dovrebbe visualizzarlo abbastanza bene:

Illustrazione delle implementazioni del metodo

Ora ci sono diversi modi, è possibile definire un metodo. Ognuno si comporta in modo diverso quando viene utilizzato con l'ereditarietà. Il modo standard funziona sempre come illustrato nell'immagine sopra. Se si desidera modificare questo comportamento, è possibile associare diverse parole chiave al metodo.

1. Classi astratte

Il primo è abstract. abstracti metodi indicano semplicemente il nulla:

Illustrazione di classi astratte

Se la tua classe contiene membri astratti, deve anche essere contrassegnata come abstract , altrimenti il ​​compilatore non compilerà la tua applicazione. Non è possibile creare istanze di abstractclassi, ma è possibile ereditare da esse e creare istanze delle classi ereditate e accedervi utilizzando la definizione della classe di base. Nel tuo esempio questo sembrerebbe:

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 chiamato, il comportamento di ShowInfo varia in base all'implementazione:

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

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

Tutti e due, Student s e Teachers sono Persons, ma si comportano in modo diverso quando viene chiesto di richiedere informazioni su se stessi. Tuttavia, il modo per chiedere loro di richiedere le loro informazioni è lo stesso: utilizzando l' Personinterfaccia di classe.

Quindi cosa succede dietro le quinte, quando erediti Person? Durante l'implementazione ShowInfo, il puntatore non punta più verso il nulla , ora punta all'implementazione effettiva! Quando si crea Studentun'istanza, indicaStudent s ShowInfo:

Illustrazione dei metodi ereditati

2. Metodi virtuali

Il secondo modo è usare i virtualmetodi. Il comportamento è lo stesso, tranne per il fatto che stai fornendo un'implementazione predefinita opzionale nella tua classe base. Le classi con virtualmembri possono essere istanziate, tuttavia le classi ereditate possono fornire implementazioni diverse. Ecco come dovrebbe effettivamente funzionare il tuo codice:

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 differenza fondamentale è che il membro di base Person.ShowInfonon punta più verso il nulla . Questo è anche il motivo per cui è possibile creare istanze di Person(e quindi non è necessario contrassegnarlo come abstractpiù):

Illustrazione di un membro virtuale all'interno di una classe base

Dovresti notare che per ora non sembra diverso dalla prima immagine. Questo perché il virtualmetodo punta a un'implementazione " il modo standard ". Utilizzando virtual, si può dire Persons, che possono (non è necessario ) fornire un'implementazione diversa per ShowInfo. Se fornisci un'implementazione diversa (usando override), come ho fatto per quanto Teachersopra, l'immagine sembrerebbe la stessa di abstract. Immagina, non abbiamo fornito un'implementazione personalizzata per Students:

public class Student : Person
{
}

Il codice si chiamerebbe così:

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

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

E l'immagine per Studentsarebbe simile a questa:

Illustrazione dell'implementazione predefinita di un metodo, usando virtual-keyword

3. La magica `nuova` parola chiave alias" Shadowing "

newè più un trucco intorno a questo. È possibile fornire metodi in classi generalizzate, che hanno gli stessi nomi dei metodi nella classe / interfaccia di base. Entrambi indicano la propria implementazione personalizzata:

Illustrazione del "modo per aggirare" utilizzando la nuova parola chiave

L'implementazione è simile a quella fornita. Il comportamento differisce, in base al modo in cui accedi al metodo:

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

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

Questo comportamento può essere voluto, ma nel tuo caso è fuorviante.

Spero che questo renda le cose più chiare da capire per te!


9
Grazie per l'ottima risposta

6
Cosa hai usato per generare quei diagrammi?
BlueRaja - Danny Pflughoeft,

2
Risposta eccellente e molto approfondita.
Nik Bougalis,

8
tl; dr hai usato newche interrompe l'ereditarietà della funzione e rende la nuova funzione separata dalla funzione della superclasse
ratchet freak

3
@Taymon: In realtà no ... Volevo solo chiarire che la chiamata ora va contro Person, no Student;)
Carsten,

45

Il polimorfismo dei sottotipi in C # usa la virtualità esplicita, simile a C ++ ma a differenza di Java. Ciò significa che è necessario contrassegnare esplicitamente i metodi come sostituibili (ovvero virtual). In C # devi anche contrassegnare esplicitamente i metodi di override come override (cioè override) per prevenire errori di battitura.

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

Nel codice nella tua domanda, usi new, che fa ombra invece di sovrascrivere. L'ombreggiatura influisce semplicemente sulla semantica del tempo di compilazione piuttosto che sulla semantica del runtime, quindi sull'output non intenzionale.


4
Chi può dire che l'OP sa cosa significano quelli.
Cole Johnson,

@ColeJohnson Aggiungerò un chiarimento.

25

Devi rendere virtuale il metodo e devi sovrascrivere la funzione nella classe figlio, al fine di chiamare il metodo dell'oggetto classe che hai inserito come riferimento della classe genitore.

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

Metodi virtuali

Quando viene invocato un metodo virtuale, il tipo di runtime dell'oggetto viene verificato per un membro di sostituzione. Viene chiamato il membro principale nella classe più derivata, che potrebbe essere il membro originale, se nessuna classe derivata ha sostituito il membro. Per impostazione predefinita, i metodi non sono virtuali. Non è possibile ignorare un metodo non virtuale. Non è possibile utilizzare il modificatore virtuale con i modificatori statici, astratti, privati ​​o di sostituzione, MSDN .

Usando Nuovo per Shadowing

Stai usando una nuova parola chiave invece di sovrascrivere, questo è ciò che fa il nuovo

  • Se il metodo nella classe derivata non è preceduto da parole chiave nuove o di sostituzione, il compilatore emetterà un avviso e il metodo si comporterà come se fosse presente la nuova parola chiave.

  • Se il metodo nella classe derivata è preceduto dalla nuova parola chiave, il metodo è definito come indipendente dal metodo nella classe base , questo articolo MSDN lo spiega molto bene.

Legatura anticipata VS Legatura tardiva

Abbiamo un'associazione anticipata in fase di compilazione per il metodo normale (non virtuale) che è il caso corrente che il compilatore assocerà la chiamata al metodo della classe base che è il metodo del tipo di riferimento (classe base) invece che l'oggetto è contenuto nel riferimento della base class cioè oggetto di classe derivato . Questo perché ShowInfonon è un metodo virtuale. L'associazione tardiva viene eseguita in fase di esecuzione per (metodo virtuale / ignorato) utilizzando la tabella dei metodi virtuali (vtable).

Per una normale funzione, il compilatore può calcolarne la posizione numerica in memoria. Quindi quando viene chiamata la funzione può generare un'istruzione per chiamare la funzione a questo indirizzo.

Per un oggetto che ha metodi virtuali, il compilatore genererà una v-table. Questo è essenzialmente un array che contiene gli indirizzi dei metodi virtuali. Ogni oggetto che ha un metodo virtuale conterrà un membro nascosto generato dal compilatore che è l'indirizzo della v-table. Quando viene chiamata una funzione virtuale, il compilatore elaborerà quale sia la posizione del metodo appropriato nella v-table. Quindi genererà il codice per cercare nella v-table degli oggetti e chiamare il metodo virtuale in questa posizione, Riferimento .


7

Voglio basarmi sulla risposta di Achratt . Per completezza, la differenza è che l'OP si aspetta che la newparola chiave nel metodo della classe derivata sovrascriva il metodo della classe base. Ciò che effettivamente fa è nascondere il metodo della classe base.

In C #, come menzionato in un'altra risposta, l'override del metodo tradizionale deve essere esplicito; il metodo della classe base deve essere contrassegnato come virtuale la classe derivata deve specificatamenteoverride il metodo della classe base. In questo caso, non importa se l'oggetto viene trattato come un'istanza della classe base o della classe derivata; il metodo derivato viene trovato e chiamato. Questo viene fatto in modo simile a quello in C ++; un metodo contrassegnato "virtuale" o "override", una volta compilato, viene risolto "in ritardo" (in fase di esecuzione) determinando il tipo effettivo dell'oggetto a cui si fa riferimento e attraversando la gerarchia dell'oggetto verso il basso lungo l'albero dal tipo di variabile al tipo di oggetto effettivo, per trovare l'implementazione più derivata del metodo definito dal tipo di variabile.

Ciò differisce da Java, che consente "sostituzioni implicite"; per esempio metodi (non statici), la semplice definizione di un metodo con la stessa firma (nome e numero / tipo di parametri) farà sì che la sottoclasse abbia la precedenza sulla superclasse.

Poiché è spesso utile estendere o sovrascrivere la funzionalità di un metodo non virtuale che non si controlla, C # include anche la newparola chiave contestuale. La newparola chiave "nasconde" il metodo genitore invece di sovrascriverlo. Qualsiasi metodo ereditabile può essere nascosto indipendentemente dal fatto che sia virtuale; questo ti consente, lo sviluppatore, di sfruttare i membri che desideri ereditare da un genitore, senza dover aggirare quelli che non ti piacciono, consentendo comunque di presentare la stessa "interfaccia" ai consumatori del tuo codice.

L'occultamento funziona in modo simile all'override dal punto di vista di una persona che utilizza l'oggetto al livello o al di sotto del livello di eredità in base al quale viene definito il metodo di occultamento. Dall'esempio della domanda, un programmatore che crea un Insegnante e memorizza quel riferimento in una variabile del tipo Insegnante vedrà il comportamento dell'implementazione di ShowInfo () da Insegnante, che nasconde quello da Persona. Tuttavia, qualcuno che lavora con il tuo oggetto in una raccolta di record Person (come sei) vedrà il comportamento dell'implementazione Person di ShowInfo (); poiché il metodo di Teacher non sovrascrive il suo genitore (che richiederebbe anche che Person.ShowInfo () sia virtuale), il codice che funziona a livello di astrazione Person non troverà l'implementazione di Teacher e non la utilizzerà.

Inoltre, non solo la newparola chiave lo farà esplicitamente, C # consente di nascondere implicitamente il metodo; la semplice definizione di un metodo con la stessa firma di un metodo della classe genitore, senza overrideo new, lo nasconderà (sebbene produrrà un avviso del compilatore o un reclamo da alcuni assistenti di refactoring come ReSharper o CodeRush). Questo è il compromesso che i progettisti di C # hanno escogitato tra le sostituzioni esplicite di C ++ rispetto a quelle implicite di Java e, sebbene sia elegante, non sempre produce il comportamento che ci si aspetterebbe se si proviene da uno sfondo in una delle lingue precedenti.

Ecco le novità: questo diventa complesso quando si combinano le due parole chiave in una lunga catena di eredità. Considera quanto segue:

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

Produzione:

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

Il primo set di cinque è tutto da aspettarsi; poiché ogni livello ha un'implementazione e fa riferimento a un oggetto dello stesso tipo come istanziato, il runtime risolve ogni chiamata al livello di ereditarietà a cui fa riferimento il tipo di variabile.

Il secondo set di cinque è il risultato dell'assegnazione di ciascuna istanza a una variabile del tipo parent immediato. Ora, alcune differenze nel comportamento si scuotono; foo2, che in realtà è un Barcast come a Foo, troverà comunque il metodo più derivato dell'attuale tipo di oggetto Bar. bar2è un Baz, ma a differenza di foo2, poiché Baz non ignora esplicitamente l'implementazione di Bar (non può; Bar sealedit), non viene visto dal runtime quando appare "dall'alto verso il basso", quindi viene invece chiamata l'implementazione di Bar. Nota che Baz non deve usare la newparola chiave; riceverai un avviso del compilatore se ometti la parola chiave, ma il comportamento implicito in C # è quello di nascondere il metodo parent. baz2è un Bai, che sostituisce quello Bazdinewimplementazione, quindi il suo comportamento è simile a quello foo2; viene chiamata l'implementazione del tipo di oggetto reale in Bai. bai2è un Bat, che nasconde di nuovo l' Baiimplementazione del metodo del suo genitore , e si comporta come bar2se l'implementazione di Bai non fosse sigillata, quindi teoricamente Bat avrebbe potuto ignorare invece di nascondere il metodo. Infine, bat2è un Bak, che non ha un'implementazione prevalente di alcun tipo e usa semplicemente quello del suo genitore.

La terza serie di cinque illustra il comportamento di risoluzione top-down completo. Tutto fa effettivamente riferimento a un'istanza della classe più derivata nella catena, Bakma la risoluzione ad ogni livello di tipo variabile viene eseguita iniziando a quel livello della catena di ereditarietà e analizzando il superamento esplicito più derivato del metodo, che sono quelli Bar, Baie Bat. Il metodo nascosto in tal modo "rompe" la catena ereditaria prevalente; devi lavorare con l'oggetto al livello o al di sotto del livello di ereditarietà che nasconde il metodo per poter utilizzare il metodo nascosto. Altrimenti, il metodo nascosto viene "scoperto" e utilizzato al suo posto.


4

Si prega di leggere sul polimorfismo in C #: Polymorphism (Guida per programmatori C #)

Questo è un esempio da lì:

Quando viene utilizzata la nuova parola chiave, vengono chiamati i nuovi membri della classe anziché i membri della classe base che sono stati sostituiti. Quei membri della classe base sono chiamati membri nascosti. I membri nascosti della classe possono comunque essere chiamati se un'istanza della classe derivata viene trasmessa a un'istanza della classe base. Per esempio:

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

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

3

Devi farlo virtuale poi sovrascrivere quella funzione Teacher. Mentre erediti e usi il puntatore di base per fare riferimento a una classe derivata, devi sovrascriverla usando virtual. newserve per nascondere il basemetodo di classe su un riferimento di classe derivato e non su un baseriferimento di classe.


3

Vorrei aggiungere un paio di altri esempi per espandere le informazioni intorno a questo. Spero che questo aiuti anche:

Ecco un esempio di codice che cancella l'aria attorno a ciò che accade quando un tipo derivato viene assegnato a un tipo base. Quali metodi sono disponibili e la differenza tra metodi sostituiti e metodi nascosti in questo contesto.

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

Un'altra piccola anomalia è quella, per la seguente riga di codice:

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

Il compilatore VS (intellisense) mostrerebbe a.foo () come A.foo ().

Pertanto, è chiaro che quando un tipo più derivato viene assegnato a un tipo base, la variabile "tipo base" funge da tipo base fino a quando viene fatto riferimento a un metodo che viene sovrascritto in un tipo derivato. Questo può diventare un po 'contro-intuitivo con metodi nascosti o metodi con lo stesso nome (ma non sovrascritti) tra i tipi padre e figlio.

Questo esempio di codice dovrebbe aiutare a delineare questi avvertimenti!


2

C # è diverso da java nel comportamento di override della classe genitore / figlio. Per impostazione predefinita in Java tutti i metodi sono virtuali, quindi il comportamento desiderato è supportato immediatamente.

In C # devi contrassegnare un metodo come virtuale nella classe base, quindi otterrai ciò che desideri.


2

La nuova parola chiave indica che il metodo nella classe corrente funzionerà solo se un'istanza della classe Insegnante è memorizzata in una variabile di tipo Insegnante. Oppure puoi attivarlo usando i casting: ((Insegnante) Person) .ShowInfo ()


1

Il tipo di variabile "insegnante" qui è typeof(Person)e questo tipo non conosce nulla della classe Insegnante e non cerca di cercare alcun metodo nei tipi derivati. Per chiamare il metodo della classe insegnante si dovrebbe lanciare la variabile: (person as Teacher).ShowInfo().

Per chiamare un metodo specifico basato sul tipo di valore, è necessario utilizzare la parola chiave "virtuale" nella classe di base e sovrascrivere i metodi virtuali nelle classi derivate. Questo approccio consente di implementare classi derivate con o senza sovrascrittura di metodi virtuali. Verranno chiamati metodi di classe base per i tipi senza virtual sovrapposti.

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

Potrebbe essere troppo tardi ... Ma la domanda è semplice e la risposta dovrebbe avere lo stesso livello di complessità.

Nel tuo codice la persona non sa nulla di Teacher.ShowInfo (). Non è possibile chiamare l'ultimo metodo dal riferimento della classe di base, perché non è virtuale.

Esiste un approccio utile all'ereditarietà: prova a immaginare cosa vuoi dire con la tua gerarchia di codice. Prova anche a immaginare cosa dice uno o un altro strumento su se stesso. Ad esempio, se si aggiunge una funzione virtuale in una classe base, si suppone: 1. può avere un'implementazione predefinita; 2. potrebbe essere reimplementato in classe derivata. Se aggiungi una funzione astratta significa solo una cosa: la sottoclasse deve creare un'implementazione. Ma nel caso in cui tu abbia una semplice funzione, non ti aspetti che nessuno cambi la sua implementazione.


0

Il compilatore lo fa perché non sa che è un Teacher. Tutto quello che sa è che è un Persono qualcosa derivato da esso. Quindi tutto ciò che può fare è chiamare il Person.ShowInfo()metodo.


0

Volevo solo dare una breve risposta -

Dovresti usare virtuale overridein classi che potrebbero essere sovrascritte. Utilizzare virtualper metodi che possono essere sostituiti da classi figlio e utilizzare overrideper metodi che devono sovrascrivere tali virtualmetodi.


0

Ho scritto lo stesso codice che hai menzionato sopra in Java, ad eccezione di alcune modifiche e ha funzionato bene come escluso. Il metodo della classe base viene ignorato e quindi l'output visualizzato è "I am Teacher".

Motivo: mentre stiamo creando un riferimento alla classe base (che è in grado di avere istanze di riferimento della classe derivata) che in realtà contiene il riferimento della classe derivata. E siccome sappiamo che l'istanza considera sempre prima i suoi metodi se la trova lì la esegue, e se non trova la definizione lì sale nella gerarchia.

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

Basandoci sull'eccellente dimostrazione di Keith S. e sulle risposte di qualità di tutti gli altri e per la massima completezza, andiamo avanti e lanciamo implementazioni esplicite dell'interfaccia per dimostrare come funziona. Considera quanto segue:

spazio dei nomi 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());
    }
}

}

Ecco l'output:

persona: I am Person == LinqConsoleApp.Teacher

insegnante: I am Teacher == LinqConsoleApp.Teacher

person1: I am Teacher == LinqConsoleApp.Teacher

person2: I am Teacher == LinqConsoleApp.Teacher

insegnante1: I am Teacher == LinqConsoleApp.Teacher

person4: I am Person == LinqConsoleApp.Person

person3: I am interface Person == LinqConsoleApp.Person

Due cose da notare:
il metodo Teacher.ShowInfo () omette la nuova parola chiave. Quando si omette new, il comportamento del metodo è lo stesso che se la nuova parola chiave fosse stata definita in modo esplicito.

È possibile utilizzare la parola chiave override solo insieme alla parola chiave virtuale. Il metodo della classe base deve essere virtuale. O astratto nel qual caso la classe deve anche essere astratta.

persona ottiene l'implementazione di base di ShowInfo perché la classe Insegnante non può sovrascrivere l'implementazione di base (nessuna dichiarazione virtuale) e persona è .GetType (Insegnante), quindi nasconde l'implementazione della classe Insegnante.

insegnante ottiene l'implementazione derivata da Insegnante di ShowInfo perché insegnante perché è Typeof (Insegnante) e non è a livello di eredità della Persona.

person1 ottiene l'implementazione dell'insegnante derivata perché è .GetType (Insegnante) e la nuova parola chiave implicita nasconde l'implementazione di base.

person2 ottiene anche l'implementazione di Teacher derivata anche se implementa IPerson e ottiene un cast esplicito per IPerson. Questo è di nuovo perché la classe Teacher non implementa esplicitamente il metodo IPerson.ShowInfo ().

insegnante1 ottiene anche l'implementazione dell'insegnante derivata perché è .GetType (Insegnante).

Solo person3 ottiene l'implementazione IPerson di ShowInfo perché solo la classe Person implementa esplicitamente il metodo e person3 è un'istanza del tipo IPerson.

Per implementare esplicitamente un'interfaccia è necessario dichiarare un'istanza var del tipo di interfaccia di destinazione e una classe deve implementare esplicitamente (qualificare completamente) i membri dell'interfaccia.

Si noti che nemmeno person4 ottiene l'implementazione di IPerson.ShowInfo. Questo perché anche se person4 è .GetType (Person) e anche se Person implementa IPerson, person4 non è un'istanza di IPerson.


Vedo che la formattazione corretta del codice presenta un po 'di sfida. Non c'è tempo per abbastanza in su in questo momento ...
Steely

0

Esempio di LinQPad per avviare alla cieca e ridurre la duplicazione del codice. Penso che sia quello che stavi cercando di fare.

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();
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.