Warum ruft das Aufrufen einer Methode in meiner abgeleiteten Klasse die Basisklassenmethode auf?

146

Betrachten Sie diesen Code:

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

Wenn ich diesen Code ausführe, wird Folgendes ausgegeben:

Ich bin Person

Sie können jedoch sehen, dass es sich um eine Instanz von handelt Teacher, nicht um Person. Warum macht der Code das?

bläulich
quelle
3
Frage von einer Java-Person: ist die Console.ReadLine (); notwendig für dieses Beispiel?
Rich
2
@ Shahrooz Ich kann deine Frage nicht beantworten - ich kenne C # nicht. Ich habe eine sehr triviale C # -Frage gestellt, nämlich ob der Aufruf von ReadLine in der Hauptmethode erforderlich ist, um WriteLine in den Klassen Person und Lehrer aufrufen zu können.
Rich
6
Ja, .Net schließt das Konsolenfenster automatisch, wenn Main () beendet wird. Um dies zu umgehen, verwenden wir Console.Read () oder Console.Readline (), um auf zusätzliche Eingaben zu warten, damit die Konsole geöffnet bleibt.
Kapitän Kenpachi
15
@Rich nein, es ist nicht erforderlich , aber Sie werden es häufig aus diesem Grund sehen: Wenn Sie ein Konsolenprogramm in Visual Studio ausführen, wird das Programmfenster beim Beenden des Programms sofort geschlossen. Wenn Sie also die Programmausgabe sehen möchten, müssen Sie dies mitteilen warten.
AakashM
1
@AakashM Danke - Ich verbringe meine Zeit in Eclipse, wo die Konsole Teil des Eclipse-Fensters ist und daher nicht geschlossen wird. Das macht durchaus Sinn.
Rich

Antworten:

368

Es gibt einen Unterschied zwischen newund virtual/ override.

Sie können sich vorstellen, dass eine Klasse, wenn sie instanziiert wird, nichts weiter als eine Tabelle von Zeigern ist, die auf die tatsächliche Implementierung ihrer Methoden verweisen. Das folgende Bild sollte dies ziemlich gut veranschaulichen:

Illustration von Methodenimplementierungen

Nun gibt es verschiedene Möglichkeiten, eine Methode zu definieren. Jedes verhält sich anders, wenn es mit Vererbung verwendet wird. Die Standardmethode funktioniert immer wie im obigen Bild dargestellt. Wenn Sie dieses Verhalten ändern möchten, können Sie Ihrer Methode verschiedene Schlüsselwörter hinzufügen.

1. Abstrakte Klassen

Der erste ist abstract. abstractMethoden zeigen einfach auf nichts:

Illustration von abstrakten Klassen

Wenn Ihre Klasse abstrakte Elemente enthält, muss sie auch als markiert werden, da abstractsonst der Compiler Ihre Anwendung nicht kompiliert. Sie können keine Instanzen von abstractKlassen erstellen , aber Sie können von ihnen erben und Instanzen Ihrer geerbten Klassen erstellen und mithilfe der Basisklassendefinition darauf zugreifen. In Ihrem Beispiel würde dies so aussehen:

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

Wenn aufgerufen, ShowInfovariiert das Verhalten von je nach Implementierung:

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

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

Sowohl Students als auch Teachers sind Persons, aber sie verhalten sich unterschiedlich, wenn sie aufgefordert werden, Informationen über sich selbst einzugeben. Die Art und Weise, wie sie aufgefordert werden, ihre Informationen einzugeben, ist jedoch dieselbe: Verwenden der PersonKlassenschnittstelle.

Was passiert also hinter den Kulissen, wenn Sie von erben Person? Bei der Implementierung ShowInfozeigt der Zeiger nicht mehr auf das Nirgendwo , sondern auf die tatsächliche Implementierung! Beim Erstellen einer StudentInstanz zeigt es auf Students ShowInfo:

Illustration geerbter Methoden

2. Virtuelle Methoden

Der zweite Weg ist die Verwendung von virtualMethoden. Das Verhalten ist dasselbe, außer dass Sie eine optionale Standardimplementierung in Ihrer Basisklasse bereitstellen . Klassen mit virtualMitgliedern können instanziiert werden, geerbte Klassen können jedoch unterschiedliche Implementierungen bereitstellen. So sollte Ihr Code tatsächlich aussehen, um zu funktionieren:

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

Der Hauptunterschied besteht darin, dass das Basismitglied nicht mehr Person.ShowInfoauf das Nirgendwo zeigt . Dies ist auch der Grund, warum Sie Instanzen von erstellen können Person(und daher nicht mehr als gekennzeichnet abstractwerden müssen):

Illustration eines virtuellen Mitglieds innerhalb einer Basisklasse

Sie sollten beachten, dass dies vorerst nicht anders aussieht als das erste Bild. Dies liegt daran, dass die virtualMethode auf eine Implementierung "auf die Standardmethode " verweist . Mit virtualkönnen Sie feststellen Persons, dass sie eine andere Implementierung für bereitstellen können (nicht müssen ) ShowInfo. Wenn Sie eine andere Implementierung (mit override) bereitstellen , wie ich es Teacheroben getan habe , würde das Bild genauso aussehen wie für abstract. Stellen Sie sich vor, wir haben keine benutzerdefinierte Implementierung für Students bereitgestellt:

public class Student : Person
{
}

Der Code würde so heißen:

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

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

Und das Bild für Studentwürde so aussehen:

Abbildung der Standardimplementierung einer Methode mit dem Schlüsselwort virtual

3. Das magische "neue" Schlüsselwort, auch bekannt als "Shadowing"

newist eher ein Hack um dieses. Sie können Methoden in verallgemeinerten Klassen bereitstellen, die dieselben Namen wie Methoden in der Basisklasse / Schnittstelle haben. Beide verweisen auf ihre eigene, benutzerdefinierte Implementierung:

Illustration des "Weges" mit dem Schlüsselwort new

Die Implementierung sieht wie die von Ihnen bereitgestellte aus. Das Verhalten hängt davon ab, wie Sie auf die Methode zugreifen:

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

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

Dieses Verhalten kann gewünscht werden, ist aber in Ihrem Fall irreführend.

Ich hoffe, das macht das Verständnis für Sie klarer!

Carsten
quelle
9
Vielen Dank für Ihre tolle Antwort
6
Womit haben Sie diese Diagramme erstellt?
BlueRaja - Danny Pflughoeft
2
Ausgezeichnete und sehr gründliche Antwort.
Nik Bougalis
8
tl; dr Sie verwendeten newdie Pause Vererbung der Funktion und die neue Funktion Funktion trennen von der übergeordneten Klasse macht
Ratsche Freak
3
@Taymon: Eigentlich nicht ... Ich wollte nur klarstellen, dass der Anruf jetzt gegen geht Person, nicht Student;)
Carsten
45

Der Subtyp-Polymorphismus in C # verwendet explizite Virtualität, ähnlich wie in C ++, jedoch im Gegensatz zu Java. Dies bedeutet, dass Sie Methoden explizit als überschreibbar markieren müssen (dh virtual). In C # müssen Sie außerdem überschreibende Methoden explizit als überschreibend markieren (dh override), um Tippfehler zu vermeiden.

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

In dem Code in Ihrer Frage verwenden Sie new, der Shadowing statt Overriding ausführt. Das Abschatten wirkt sich lediglich auf die Semantik zur Kompilierungszeit und nicht auf die Laufzeitsemantik aus, daher auf die unbeabsichtigte Ausgabe.

Gemeinschaft
quelle
4
Wer sagt, dass das OP weiß, was diese bedeuten.
Cole Johnson
@ ColeJohnson Ich werde eine Klarstellung hinzufügen.
25

Sie müssen die Methode virtuell machen und die Funktion in der untergeordneten Klasse überschreiben, um die Methode des Klassenobjekts aufzurufen, die Sie in die übergeordnete Klassenreferenz eingefügt haben.

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

Virtuelle Methoden

Wenn eine virtuelle Methode aufgerufen wird, wird der Laufzeittyp des Objekts auf ein überschreibendes Mitglied überprüft. Das überschreibende Mitglied in der am meisten abgeleiteten Klasse wird aufgerufen. Dies kann das ursprüngliche Mitglied sein, wenn keine abgeleitete Klasse das Mitglied überschrieben hat. Standardmäßig sind Methoden nicht virtuell. Sie können eine nicht virtuelle Methode nicht überschreiben. Sie können den virtuellen Modifikator nicht mit den statischen, abstrakten, privaten oder Überschreibungsmodifikatoren MSDN verwenden .

Verwenden von New for Shadowing

Sie verwenden ein neues Schlüsselwort, anstatt es zu überschreiben. Dies ist, was new tut

  • Wenn der Methode in der abgeleiteten Klasse keine neuen oder überschriebenen Schlüsselwörter vorangestellt sind, gibt der Compiler eine Warnung aus und die Methode verhält sich so, als ob das neue Schlüsselwort vorhanden wäre.

  • Wenn der Methode in der abgeleiteten Klasse das neue Schlüsselwort vorangestellt wird, wird die Methode als unabhängig von der Methode in der Basisklasse definiert . In diesem MSDN-Artikel wird dies sehr gut erläutert.

Frühe Bindung VS Späte Bindung

Wir haben eine frühe Bindung zur Kompilierungszeit für die normale Methode (nicht virtuell). Dies ist der aktuelle Fall, in dem der Compiler den Aufruf an die Methode der Basisklasse bindet, die eine Methode vom Referenztyp (Basisklasse) ist, anstatt dass das Objekt in der Referenz der Basis gehalten wird Klasse dh abgeleitetes Klassenobjekt . Dies liegt daran, dass ShowInfoes sich nicht um eine virtuelle Methode handelt. Die späte Bindung wird zur Laufzeit für (virtuelle / überschriebene Methode) unter Verwendung der virtuellen Methodentabelle (vtable) durchgeführt.

Für eine normale Funktion kann der Compiler die numerische Position im Speicher ermitteln. Wenn die Funktion aufgerufen wird, kann sie eine Anweisung zum Aufrufen der Funktion an dieser Adresse generieren.

Für ein Objekt mit virtuellen Methoden generiert der Compiler eine V-Tabelle. Dies ist im Wesentlichen ein Array, das die Adressen der virtuellen Methoden enthält. Jedes Objekt mit einer virtuellen Methode enthält ein verstecktes Element, das vom Compiler generiert wird und die Adresse der V-Tabelle ist. Wenn eine virtuelle Funktion aufgerufen wird, ermittelt der Compiler die Position der entsprechenden Methode in der V-Tabelle. Anschließend wird Code generiert, der in die V-Tabelle der Objekte schaut und die virtuelle Methode an dieser Position, Referenz , aufruft .

Adil
quelle
7

Ich möchte auf Achratts Antwort aufbauen . Der Vollständigkeit halber besteht der Unterschied darin, dass das OP erwartet, dass das newSchlüsselwort in der Methode der abgeleiteten Klasse die Basisklassenmethode überschreibt. Was es tatsächlich tut, ist die Basisklassenmethode auszublenden .

In C # muss, wie in einer anderen Antwort erwähnt, das Überschreiben traditioneller Methoden explizit sein. Die Basisklassenmethode muss als markiert sein virtualund die abgeleitete Klasse muss spezifisch seinoverride die Basisklassenmethode sein. In diesem Fall spielt es keine Rolle, ob das Objekt als Instanz der Basisklasse oder der abgeleiteten Klasse behandelt wird. Die abgeleitete Methode wird gefunden und aufgerufen. Dies geschieht auf ähnliche Weise wie in C ++. Eine mit "virtuell" oder "überschreiben" gekennzeichnete Methode wird beim Kompilieren "spät" (zur Laufzeit) aufgelöst, indem der tatsächliche Typ des referenzierten Objekts ermittelt und die Objekthierarchie entlang des Baums vom Variablentyp zum tatsächlichen Objekttyp nach unten durchlaufen wird. um die am meisten abgeleitete Implementierung der durch den Variablentyp definierten Methode zu finden.

Dies unterscheidet sich von Java, das "implizite Überschreibungen" ermöglicht. Beispielsweise führen Methoden (nicht statisch) durch einfaches Definieren einer Methode mit derselben Signatur (Name und Anzahl / Art der Parameter) dazu, dass die Unterklasse die Oberklasse überschreibt.

Da es häufig nützlich ist, die Funktionalität einer nicht virtuellen Methode, die Sie nicht steuern, zu erweitern oder zu überschreiben, enthält C # auch das newkontextbezogene Schlüsselwort. Das newSchlüsselwort "verbirgt" die übergeordnete Methode, anstatt sie zu überschreiben. Jede vererbbare Methode kann ausgeblendet werden, unabhängig davon, ob sie virtuell ist oder nicht. Auf diese Weise können Sie als Entwickler die Mitglieder nutzen, die Sie von einem Elternteil erben möchten, ohne die Mitglieder umgehen zu müssen, die Sie nicht haben, und gleichzeitig den Verbrauchern Ihres Codes dieselbe "Schnittstelle" präsentieren.

Das Ausblenden funktioniert ähnlich wie das Überschreiben aus der Perspektive einer Person, die Ihr Objekt auf oder unter der Vererbungsebene verwendet, auf der die Ausblendmethode definiert ist. Aus dem Beispiel der Frage geht hervor, dass ein Codierer, der einen Lehrer erstellt und diese Referenz in einer Variablen des Lehrertyps speichert, das Verhalten der ShowInfo () -Implementierung von Teacher erkennt, wodurch die Implementierung von Person ausgeblendet wird. Jemand, der mit Ihrem Objekt in einer Sammlung von Personendatensätzen arbeitet (wie Sie), sieht jedoch das Verhalten der Person-Implementierung von ShowInfo (). Da die Methode des Lehrers die übergeordnete Methode nicht überschreibt (für die auch Person.ShowInfo () virtuell sein müsste), findet Code, der auf der Person-Abstraktionsebene arbeitet, die Lehrer-Implementierung nicht und verwendet sie nicht.

Darüber hinaus wird das newSchlüsselwort dies nicht nur explizit tun, C # ermöglicht auch das Ausblenden impliziter Methoden. Durch einfaches Definieren einer Methode mit derselben Signatur wie eine übergeordnete Klassenmethode ohne overrideoder newwird diese ausgeblendet (obwohl eine Compiler-Warnung oder eine Beschwerde von bestimmten Refactoring-Assistenten wie ReSharper oder CodeRush ausgegeben wird). Dies ist der Kompromiss, den die Designer von C # zwischen den expliziten Überschreibungen von C ++ und den impliziten von Java gefunden haben. Obwohl es elegant ist, erzeugt es nicht immer das Verhalten, das Sie erwarten würden, wenn Sie aus einem Hintergrund in einer der älteren Sprachen stammen.

Hier ist das Neue: Dies wird komplex, wenn Sie die beiden Schlüsselwörter in einer langen Vererbungskette kombinieren. Folgendes berücksichtigen:

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

Ausgabe:

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

Der erste Satz von fünf ist alles zu erwarten; Da jede Ebene eine Implementierung hat und als Objekt des gleichen Typs referenziert wird, der instanziiert wurde, löst die Laufzeit jeden Aufruf auf die Vererbungsstufe auf, auf die der Variablentyp verweist.

Der zweite Satz von fünf ist das Ergebnis der Zuweisung jeder Instanz zu einer Variablen des unmittelbaren übergeordneten Typs. Nun schütteln sich einige Unterschiede im Verhalten aus; foo2, die eigentlich eine BarBesetzung als ist Foo, findet immer noch die abgeleitete Methode des tatsächlichen Objekttyps Bar. bar2ist a Baz, aber anders als bei foo2, da Baz die Implementierung von Bar nicht explizit überschreibt (es kann nicht; Bar sealedit), wird es von der Laufzeit nicht gesehen, wenn "top-down" angezeigt wird, daher wird stattdessen die Implementierung von Bar aufgerufen. Beachten Sie, dass Baz das newSchlüsselwort nicht verwenden muss . Sie erhalten eine Compiler-Warnung, wenn Sie das Schlüsselwort weglassen. Das implizite Verhalten in C # besteht jedoch darin, die übergeordnete Methode auszublenden. baz2ist ein Bai, das Baz's überschreibtnewImplementierung, daher ist sein Verhalten ähnlich wie foo2das von; Die Implementierung des tatsächlichen Objekttyps in Bai wird aufgerufen. bai2ist eine Bat, die wiederum die BaiMethodenimplementierung der übergeordneten Methode verbirgt und sich genauso verhält bar2, obwohl die Implementierung von Bai nicht versiegelt ist. Theoretisch hätte Bat die Methode also überschreiben können, anstatt sie zu verbergen. Schließlich bat2ist a Bak, das keine übergeordnete Implementierung von beiden Arten hat und einfach die seines übergeordneten verwendet.

Der dritte Satz von fünf zeigt das vollständige Auflösungsverhalten von oben nach unten. Alles verweist tatsächlich auf eine Instanz der am meisten abgeleiteten Klasse in der Kette, Bakaber die Auflösung auf jeder Ebene des Variablentyps wird durchgeführt, indem auf dieser Ebene der Vererbungskette begonnen wird und ein Drilldown bis zur am meisten abgeleiteten expliziten Überschreibung der Methode durchgeführt wird die in Bar, Baiund Bat. Das Ausblenden einer Methode "unterbricht" somit die übergeordnete Vererbungskette. Sie müssen mit dem Objekt auf oder unter der Vererbungsebene arbeiten, die die Methode verbirgt, damit die Ausblendmethode verwendet werden kann. Andernfalls wird die versteckte Methode "aufgedeckt" und stattdessen verwendet.

KeithS
quelle
4

Bitte lesen Sie über Polymorphismus in C #: Polymorphismus (C # Programmierhandbuch)

Dies ist ein Beispiel von dort:

Wenn das neue Schlüsselwort verwendet wird, werden die neuen Klassenmitglieder anstelle der ersetzten Basisklassenmitglieder aufgerufen. Diese Basisklassenmitglieder werden versteckte Mitglieder genannt. Versteckte Klassenmitglieder können weiterhin aufgerufen werden, wenn eine Instanz der abgeleiteten Klasse in eine Instanz der Basisklasse umgewandelt wird. Beispielsweise:

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

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

Sie müssen es schaffen virtualund dann diese Funktion in überschreiben Teacher. Da Sie den Basiszeiger erben und verwenden, um auf eine abgeleitete Klasse zu verweisen, müssen Sie ihn mit überschreiben virtual. newdient zum Ausblenden der baseKlassenmethode in einer abgeleiteten Klassenreferenz und nicht in einer baseKlassenreferenz.

Jay Patel
quelle
3

Ich möchte noch ein paar Beispiele hinzufügen, um die Informationen dazu zu erweitern. Hoffe das hilft auch:

Hier ist ein Codebeispiel, das die Luft darüber frei macht, was passiert, wenn ein abgeleiteter Typ einem Basistyp zugewiesen wird. Welche Methoden sind verfügbar und der Unterschied zwischen überschriebenen und versteckten Methoden in diesem Zusammenhang.

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

Eine weitere kleine Anomalie ist die für die folgende Codezeile:

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

Der VS-Compiler (Intellisense) würde a.foo () als A.foo () anzeigen.

Daher ist es klar, dass, wenn einem Basistyp ein stärker abgeleiteter Typ zugewiesen wird, die Variable 'Basistyp' als Basistyp fungiert, bis auf eine Methode verwiesen wird, die in einem abgeleiteten Typ überschrieben wird. Dies kann bei versteckten Methoden oder Methoden mit demselben Namen (aber nicht überschrieben) zwischen dem übergeordneten und dem untergeordneten Typ etwas kontraintuitiv werden.

Dieses Codebeispiel soll helfen, diese Vorbehalte abzugrenzen!

Vaibhav
quelle
2

C # unterscheidet sich von Java im Überschreibungsverhalten der übergeordneten / untergeordneten Klasse. In Java sind standardmäßig alle Methoden virtuell, sodass das gewünschte Verhalten sofort unterstützt wird.

In C # müssen Sie eine Methode in der Basisklasse als virtuell markieren, dann erhalten Sie, was Sie wollen.

Adrian Salazar
quelle
2

Das neue Schlüsselwort gibt an, dass die Methode in der aktuellen Klasse nur funktioniert, wenn Sie eine Instanz der Klasse Teacher in einer Variablen vom Typ Teacher gespeichert haben. Oder Sie können es mit Castings auslösen: ((Lehrer) Person) .ShowInfo ()

Miguel
quelle
1

Der Typ der Variablen 'Lehrer' ist hier typeof(Person)und dieser Typ weiß nichts über die Lehrerklasse und versucht nicht, nach Methoden in abgeleiteten Typen zu suchen. Um die Methode der Lehrerklasse aufzurufen, sollten Sie Ihre Variable umwandeln : (person as Teacher).ShowInfo().

Um eine bestimmte Methode basierend auf dem Werttyp aufzurufen, sollten Sie das Schlüsselwort 'virtual' in Ihrer Basisklasse verwenden und virtuelle Methoden in abgeleiteten Klassen überschreiben. Dieser Ansatz ermöglicht die Implementierung abgeleiteter Klassen mit oder ohne Überschreiben virtueller Methoden. Methoden der Basisklasse werden für Typen ohne überlagerte Virtuals aufgerufen.

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
quelle
1

Könnte zu spät sein ... Aber die Frage ist einfach und die Antwort sollte die gleiche Komplexität haben.

In Ihrer Codevariablen weiß die Person nichts über Teacher.ShowInfo (). Es gibt keine Möglichkeit, die letzte Methode aus der Basisklassenreferenz aufzurufen, da sie nicht virtuell ist.

Es gibt einen nützlichen Ansatz für die Vererbung - stellen Sie sich vor, was Sie mit Ihrer Code-Hierarchie sagen möchten. Versuchen Sie sich auch vorzustellen, was das eine oder andere Werkzeug über sich selbst sagt. Wenn Sie beispielsweise einer Basisklasse eine virtuelle Funktion hinzufügen, nehmen Sie Folgendes an: 1. Sie kann eine Standardimplementierung haben. 2. Es kann in einer abgeleiteten Klasse erneut implementiert werden. Wenn Sie eine abstrakte Funktion hinzufügen, bedeutet dies nur eines: Die Unterklasse muss eine Implementierung erstellen. Wenn Sie jedoch eine einfache Funktion haben, erwarten Sie nicht, dass jemand die Implementierung ändert.

Illia Levandovskyi
quelle
0

Der Compiler tut dies, weil er nicht weiß, dass es sich um eine handelt Teacher. Alles was es weiß ist, dass es ein Personoder etwas davon ist. Alles was es tun kann, ist die Person.ShowInfo()Methode aufzurufen .

Cole Johnson
quelle
0

Ich wollte nur eine kurze Antwort geben -

Sie sollten virtualund overridein Klassen verwenden, die überschrieben werden könnten. Verwendung virtualfür Methoden, die von untergeordneten Klassen überschrieben werden können, und Verwendung overridefür Methoden, die solche virtualMethoden überschreiben sollten .

Shruti Kapoor
quelle
0

Ich habe den gleichen Code geschrieben, den Sie oben in Java erwähnt haben, mit Ausnahme einiger Änderungen, und er hat wie ausgenommen gut funktioniert. Die Methode der Basisklasse wird überschrieben und die angezeigte Ausgabe lautet "Ich bin Lehrer".

Grund: Während wir eine Referenz der Basisklasse erstellen (die eine referenzierende Instanz der abgeleiteten Klasse haben kann), die tatsächlich die Referenz der abgeleiteten Klasse enthält. Und da wir wissen, dass die Instanz ihre Methoden immer zuerst überprüft, wenn sie sie dort findet, führt sie sie aus, und wenn sie die Definition dort nicht findet, wird sie in der Hierarchie nach oben verschoben.

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
quelle
0

Aufbauend auf der hervorragenden Demonstration von Keith S. und den Qualitätsantworten aller anderen und der Vollständigkeit halber können wir explizite Schnittstellenimplementierungen durchführen, um zu demonstrieren, wie dies funktioniert. Beachten Sie Folgendes:

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

}}

Hier ist die Ausgabe:

Person: Ich bin Person == LinqConsoleApp.Teacher

Lehrer: Ich bin Lehrer == LinqConsoleApp.Teacher

person1: Ich bin Lehrer == LinqConsoleApp.Teacher

person2: Ich bin Lehrer == LinqConsoleApp.Teacher

Lehrer1: Ich bin Lehrer == LinqConsoleApp.Teacher

person4: Ich bin Person == LinqConsoleApp.Person

person3: Ich bin Schnittstelle Person == LinqConsoleApp.Person

Zwei Dinge zu beachten:
Die Teacher.ShowInfo () -Methode lässt das neue Schlüsselwort weg. Wenn new weggelassen wird, ist das Methodenverhalten dasselbe, als ob das neue Schlüsselwort explizit definiert worden wäre.

Sie können das Schlüsselwort override nur in Verbindung mit dem virtuellen Schlüsselwort verwenden. Die Basisklassenmethode muss virtuell sein. Oder abstrakt. In diesem Fall muss die Klasse auch abstrakt sein.

person erhält die Basisimplementierung von ShowInfo, da die Teacher-Klasse die Basisimplementierung nicht überschreiben kann (keine virtuelle Deklaration) und person .GetType (Teacher) ist, sodass die Implementierung der Teacher-Klasse ausgeblendet wird.

Der Lehrer erhält die abgeleitete Lehrerimplementierung von ShowInfo, weil der Lehrer Typeof (Lehrer) ist und sich nicht auf der Ebene der Personenvererbung befindet.

person1 ruft die abgeleitete Teacher-Implementierung ab, da es sich um .GetType (Teacher) handelt und das implizierte neue Schlüsselwort die Basisimplementierung verbirgt.

person2 erhält auch die abgeleitete Teacher-Implementierung, obwohl IPerson implementiert ist und eine explizite Umwandlung in IPerson erfolgt. Dies liegt wiederum daran, dass die Teacher-Klasse die IPerson.ShowInfo () -Methode nicht explizit implementiert.

Teacher1 erhält auch die abgeleitete Teacher-Implementierung, da es sich um .GetType (Teacher) handelt.

Nur person3 erhält die IPerson-Implementierung von ShowInfo, da nur die Person-Klasse die Methode explizit implementiert und person3 eine Instanz des IPerson-Typs ist.

Um eine Schnittstelle explizit zu implementieren, müssen Sie eine var-Instanz des Zielschnittstellentyps deklarieren, und eine Klasse muss die Schnittstellenmitglieder explizit implementieren (vollständig qualifizieren).

Beachten Sie, dass nicht einmal person4 die IPerson.ShowInfo-Implementierung erhält. Dies liegt daran, dass person4 keine Instanz von IPerson ist, obwohl person4 .GetType (Person) ist und Person IPerson implementiert.

stählern
quelle
Ich sehe, dass das richtige Formatieren von Code eine Herausforderung darstellt. Keine Zeit, es jetzt schön zu machen ...
stählern
0

LinQPad-Beispiel zum blinden Starten und Reduzieren von Code-Duplikationen. Ich denke, das haben Sie versucht.

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
quelle