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?
c#
class
derived-class
bläulich
quelle
quelle
Antworten:
Es gibt einen Unterschied zwischen
new
undvirtual
/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:
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
.abstract
Methoden zeigen einfach auf nichts:Wenn Ihre Klasse abstrakte Elemente enthält, muss sie auch als markiert werden, da
abstract
sonst der Compiler Ihre Anwendung nicht kompiliert. Sie können keine Instanzen vonabstract
Klassen 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:Wenn aufgerufen,
ShowInfo
variiert das Verhalten von je nach Implementierung:Sowohl
Student
s als auchTeacher
s sindPerson
s, 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 derPerson
Klassenschnittstelle.Was passiert also hinter den Kulissen, wenn Sie von erben
Person
? Bei der ImplementierungShowInfo
zeigt der Zeiger nicht mehr auf das Nirgendwo , sondern auf die tatsächliche Implementierung! Beim Erstellen einerStudent
Instanz zeigt es aufStudent
sShowInfo
:2. Virtuelle Methoden
Der zweite Weg ist die Verwendung von
virtual
Methoden. Das Verhalten ist dasselbe, außer dass Sie eine optionale Standardimplementierung in Ihrer Basisklasse bereitstellen . Klassen mitvirtual
Mitgliedern können instanziiert werden, geerbte Klassen können jedoch unterschiedliche Implementierungen bereitstellen. So sollte Ihr Code tatsächlich aussehen, um zu funktionieren:Der Hauptunterschied besteht darin, dass das Basismitglied nicht mehr
Person.ShowInfo
auf das Nirgendwo zeigt . Dies ist auch der Grund, warum Sie Instanzen von erstellen könnenPerson
(und daher nicht mehr als gekennzeichnetabstract
werden müssen):Sie sollten beachten, dass dies vorerst nicht anders aussieht als das erste Bild. Dies liegt daran, dass die
virtual
Methode auf eine Implementierung "auf die Standardmethode " verweist . Mitvirtual
können Sie feststellenPersons
, dass sie eine andere Implementierung für bereitstellen können (nicht müssen )ShowInfo
. Wenn Sie eine andere Implementierung (mitoverride
) bereitstellen , wie ich esTeacher
oben getan habe , würde das Bild genauso aussehen wie fürabstract
. Stellen Sie sich vor, wir haben keine benutzerdefinierte Implementierung fürStudent
s bereitgestellt:Der Code würde so heißen:
Und das Bild für
Student
würde so aussehen:3. Das magische "neue" Schlüsselwort, auch bekannt als "Shadowing"
new
ist 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:Die Implementierung sieht wie die von Ihnen bereitgestellte aus. Das Verhalten hängt davon ab, wie Sie auf die Methode zugreifen:
Dieses Verhalten kann gewünscht werden, ist aber in Ihrem Fall irreführend.
Ich hoffe, das macht das Verständnis für Sie klarer!
quelle
new
die Pause Vererbung der Funktion und die neue Funktion Funktion trennen von der übergeordneten Klasse machtPerson
, nichtStudent
;)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 (dhoverride
), um Tippfehler zu vermeiden.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.quelle
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.
Virtuelle Methoden
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
ShowInfo
es 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.quelle
Ich möchte auf Achratts Antwort aufbauen . Der Vollständigkeit halber besteht der Unterschied darin, dass das OP erwartet, dass das
new
Schlü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
virtual
und 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
new
kontextbezogene Schlüsselwort. Dasnew
Schlü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
new
Schlü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 ohneoverride
odernew
wird 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:
Ausgabe:
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 eineBar
Besetzung als istFoo
, findet immer noch die abgeleitete Methode des tatsächlichen Objekttyps Bar.bar2
ist aBaz
, aber anders als beifoo2
, da Baz die Implementierung von Bar nicht explizit überschreibt (es kann nicht; Barsealed
it), wird es von der Laufzeit nicht gesehen, wenn "top-down" angezeigt wird, daher wird stattdessen die Implementierung von Bar aufgerufen. Beachten Sie, dass Baz dasnew
Schlü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.baz2
ist einBai
, dasBaz
's überschreibtnew
Implementierung, daher ist sein Verhalten ähnlich wiefoo2
das von; Die Implementierung des tatsächlichen Objekttyps in Bai wird aufgerufen.bai2
ist eineBat
, die wiederum dieBai
Methodenimplementierung der übergeordneten Methode verbirgt und sich genauso verhältbar2
, obwohl die Implementierung von Bai nicht versiegelt ist. Theoretisch hätte Bat die Methode also überschreiben können, anstatt sie zu verbergen. Schließlichbat2
ist aBak
, 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,
Bak
aber 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 inBar
,Bai
undBat
. 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.quelle
Bitte lesen Sie über Polymorphismus in C #: Polymorphismus (C # Programmierhandbuch)
Dies ist ein Beispiel von dort:
quelle
Sie müssen es schaffen
virtual
und dann diese Funktion in überschreibenTeacher
. Da Sie den Basiszeiger erben und verwenden, um auf eine abgeleitete Klasse zu verweisen, müssen Sie ihn mit überschreibenvirtual
.new
dient zum Ausblenden derbase
Klassenmethode in einer abgeleiteten Klassenreferenz und nicht in einerbase
Klassenreferenz.quelle
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.
Eine weitere kleine Anomalie ist die für die folgende Codezeile:
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!
quelle
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.
quelle
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 ()
quelle
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.
quelle
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.
quelle
Der Compiler tut dies, weil er nicht weiß, dass es sich um eine handelt
Teacher
. Alles was es weiß ist, dass es einPerson
oder etwas davon ist. Alles was es tun kann, ist diePerson.ShowInfo()
Methode aufzurufen .quelle
Ich wollte nur eine kurze Antwort geben -
Sie sollten
virtual
undoverride
in Klassen verwenden, die überschrieben werden könnten. Verwendungvirtual
für Methoden, die von untergeordneten Klassen überschrieben werden können, und Verwendungoverride
für Methoden, die solchevirtual
Methoden überschreiben sollten .quelle
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.
quelle
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 {
}}
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.
quelle
LinQPad-Beispiel zum blinden Starten und Reduzieren von Code-Duplikationen. Ich denke, das haben Sie versucht.
quelle