Mit Vererbung baut eine Klasse auf einer anderen auf. Die abgeleitete Klasse bekommt die Member der Basisklasse, fügt eigene hinzu und darf das Verhalten ersetzen, das die Basisklasse zum Ersetzen freigibt. Zusammen mit virtual-Methoden ergibt das Polymorphie: Code, der gegen die Basisklasse geschrieben ist, führt das richtige abgeleitete Verhalten aus, ohne zu wissen, welche abgeleitete Klasse er vor sich hat.
Eine Klasse ableiten
Setze die Basisklasse nach einen Doppelpunkt. Die abgeleitete Klasse hat alles, was die Basisklasse hat, plus das, was sie selbst deklariert:
Ausgabe:
TX-19: 20 km, 2 fares
True
Was eine abgeleitete Klasse bekommt und was nicht:
- Vererbt: Felder, Properties, Methoden, Events und verschachtelte Typen. Alles ist physisch im Objekt vorhanden.
- Zugänglich: nur das, was die Basis erlaubt: Member mit
public,protectedundinternal. Ein privater Member vonVehicleexistiert in jedemTaxi, aber der Code vonTaxikann ihn nicht ansprechen. Deshalb hatKmeinprivate set:Taxikann den Wert lesen und ändert ihn nur überDrive. - Nicht vererbt: Konstruktoren.
Taximuss eigene deklarieren und mit: base(plate)an einen vonVehicleverketten. Die Seite zu Konstruktoren zeigt, in welcher Reihenfolge beide laufen.
Jede Klasse leitet letztlich von object ab, deshalb hat jedes Objekt ToString(), Equals() und GetHashCode().
virtual und override
Eine Basisklasse markiert eine Methode als virtual, um zu sagen „abgeleitete Klassen dürfen eine eigene Version liefern“. Eine abgeleitete Klasse ersetzt sie mit override. Innerhalb der Überschreibung ruft base.Method() die Version der Basisklasse auf.
Ausgabe:
[email] to lea@example.com: Your order 1042 has shipped today
(unsubscribe link appended)
[sms] to +351 912 000 111: Your order 1042 has...
[generic] to ops-team: Your order 1042 has shipped today
Die Schleifenvariable hat den Typ Notification, und trotzdem stellt sich jedes Objekt auf seine eigene Weise dar. Das ist Polymorphie: Der Aufruf n.Render(...) wird zur Laufzeit anhand des tatsächlichen Typs des Objekts aufgelöst. Beachte auch, dass base.Render in Email Channel verwendet, das selbst virtuell ist, die Basismethode gibt also email aus, nicht generic. Ein virtueller Aufruf innerhalb der Basisklasse erreicht trotzdem die Überschreibung.
Auch Properties können virtuell sein, wie Channel zeigt. Felder nicht.
Der Compiler besteht auf beiden Schlüsselwörtern. override an einer Methode, die nicht virtual ist, ist Fehler CS0506 („cannot override inherited member ... because it is not marked virtual, abstract, or override“). override wegzulassen, wenn die Basismethode virtuell ist, ist nur eine Warnung, ändert aber die Bedeutung vollständig, wie der nächste Abschnitt zeigt.
new gegenüber override: verdecken statt überschreiben
Deklariert eine abgeleitete Klasse eine Methode mit derselben Signatur wie eine Basismethode, ohne override zu schreiben, verdeckt sie die Basismethode. Der Compiler warnt (CS0114 bei einer virtuellen Basismethode, sonst CS0108) und schlägt das Schlüsselwort new vor, das die Warnung zum Schweigen bringt, das Verdecken aber beibehält:
Ausgabe:
Sales report
Report
Draft report
b und c sind dieselbe Art Objekt und geben trotzdem verschiedene Titel aus. Mit new hängt die gewählte Methode vom Typ der Variable ab, entschieden zur Kompilierzeit. Code, der Berichte als Report behandelt (eine Liste, ein Methodenparameter, ein Framework-Callback), sieht DraftReport.Title nie. Das ist fast nie gewollt. Nimm override für Polymorphie; new existiert vor allem für den Fall, dass eine Basisklasse, die du nicht kontrollierst, einen Member hinzufügt, dessen Name mit einem kollidiert, den du schon hast.
sealed
sealed an einer Klasse verbietet es, von ihr abzuleiten:
sealed class Invoice { }
class CorrectedInvoice : Invoice { }
// error CS0509: 'CorrectedInvoice': cannot derive from sealed type 'Invoice'
string ist sealed, ebenso viele Framework-Typen. An einer Überschreibung beendet sealed die Kette auf dieser Ebene:
class Shape { public virtual string Name() => "shape"; }
class Square : Shape { public sealed override string Name() => "square"; }
class Tile : Square { public override string Name() => "tile"; }
// error CS0239: 'Tile.Name()': cannot override inherited member 'Square.Name()' because it is sealed
Eine Klasse für Vererbung zu entwerfen ist Arbeit: entscheiden, was virtuell ist, worauf sich abgeleitete Klassen verlassen dürfen, in welcher Reihenfolge Dinge passieren. Eine Klasse, die nicht so entworfen wurde, ist versiegelt sicherer, und das Versiegeln lässt sich später rückgängig machen, ohne jemanden zu brechen, während sich das Entsiegeln nicht zurücknehmen lässt, sobald andere von dir ableiten. Aufrufe von Membern versiegelter Klassen können außerdem etwas schneller sein, weil die Runtime weiß, dass es keine Überschreibung gibt.
Eine Basisklasse, viele Interfaces
Eine C#-Klasse hat genau eine Basisklasse. class Admin : User, Employee ist Fehler CS1721 („cannot have multiple base classes“). Eine Klasse kann aber neben ihrer Basisklasse beliebig viele Interfaces implementieren:
class Admin : User, IAuditable, IComparable<Admin>
{
// base class first, then interfaces, in any order
}
Nimm eine Basisklasse für „ist eine Art von und teilt die Implementierung mit“ und Interfaces für „kann“. Wenn die Basisklasse nur existiert, um abgeleitete Klassen zu zwingen, bestimmte Methoden auszufüllen, ist eine abstrakte Klasse das Werkzeug.
Casts nach oben und unten in der Hierarchie
Ein abgeleitetes Objekt lässt sich immer dort verwenden, wo sein Basistyp erwartet wird. Dieser Upcast ist implizit und kann nicht scheitern. Die andere Richtung, ein Downcast, braucht einen expliziten Cast und scheitert zur Laufzeit, wenn das Objekt nicht diesen Typ hat:
Ausgabe:
Rex fetches the ball
InvalidCastException: Tom is not a Dog
True
Rex fetches the ball
Dog d = pet; ohne den Cast zu schreiben ist ein Kompilierfehler (CS0266: „An explicit conversion exists (are you missing a cast?)“), weil der Compiler nur weiß, dass pet irgendein Animal ist. Nimm lieber is mit einer Variable, wenn das Objekt einen anderen Typ haben könnte, und einen einfachen Cast nur, wenn alles andere ein Bug wäre. Häufiges Downcasting ist ein Warnsignal im Design: Meist gehört das Verhalten in eine virtuelle Methode der Basisklasse. Die Seite zu Pattern Matching behandelt die Formen von is vollständig.
Vererbung und Collections
Polymorphie funktioniert Element für Element, aber generische Collections eines abgeleiteten Typs sind keine Collections des Basistyps: List<Animal> animals = new List<Dog>(); kompiliert nicht, weil die Liste dann eine Cat annehmen würde. Schreibgeschützte Sichten sind kovariant, IEnumerable<Animal> animals = new List<Dog>(); ist also in Ordnung. Die Seite zu Generics erklärt, warum.
Häufige Fehler
overridevergessen. Die Methode kompiliert mit einer Warnung und verdeckt stillschweigend, statt zu überschreiben. Behandle CS0114 als Fehler.- Alles
virtualmachen. Jeder virtuelle Member ist ein Versprechen an abgeleitete Klassen darüber, wann er aufgerufen wird und was er annehmen darf. Markiere nur die Erweiterungspunkte, die du beabsichtigst. - Tiefe Hierarchien. Drei oder vier Vererbungsebenen machen es schwer zu wissen, welche Version einer Methode läuft. Komposition, also eine Klasse, die ein anderes Objekt hält und aufruft, ist oft einfacher als eine weitere Ebene.
- Virtuelle Methoden aus einem Konstruktor aufrufen. Die Überschreibung läuft vor dem Rumpf des abgeleiteten Konstruktors, jedes Feld, das dieser Rumpf zuweist, hat also noch seinen Standardwert.
- Vererbung nur zur Wiederverwendung von Code. Erbt ein
StackvonList, können Aufrufer mitInsertmitten in deinen Stack einfügen. Halte stattdessen eineListin einem privaten Feld.
Häufig gestellte Fragen
Wie funktioniert Vererbung in C#?
Eine Klasse nennt nach einem Doppelpunkt eine Basisklasse: class Dog : Animal. Die abgeleitete Klasse bekommt alle Member der Basisklasse (Felder, Properties, Methoden, Events), kann eigene hinzufügen und die überschreiben, die die Basis als virtual markiert hat. Konstruktoren werden nicht vererbt, und private-Member sind zwar im Objekt vorhanden, aber aus der abgeleiteten Klasse nicht zugänglich.
Was ist der Unterschied zwischen virtual und override in C#?
virtual steht an der Methode der Basisklasse und sagt „abgeleitete Klassen dürfen das ersetzen“. override steht an der Methode der abgeleiteten Klasse und ersetzt sie. Beide sind nötig: Eine Methode zu überschreiben, die nicht virtual, abstract oder bereits override ist, ist Fehler CS0506. Beim Aufruf einer virtuellen Methode führt die Runtime die Version für den tatsächlichen Typ des Objekts aus, nicht für den Typ der Variable.
Was ist der Unterschied zwischen new und override in C#?
override ersetzt die Basismethode für jeden Aufrufer, auch für Code, der das Objekt über eine Variable der Basisklasse hält. new verdeckt sie nur: Code, der das Objekt als abgeleiteten Typ sieht, ruft die neue Methode auf, während Code, der es als Basistyp sieht, weiterhin die Basismethode aufruft. Base b = new Derived(); b.M(); führt also mit override Derived.M aus und mit new Base.M.
Unterstützt C# Mehrfachvererbung?
Nicht für Klassen: Eine Klasse hat genau eine Basisklasse, und zwei aufzulisten ist Fehler CS1721. Eine Klasse kann aber beliebig viele Interfaces implementieren, und so bildet C# ab, dass „dieser Typ mehrere Dinge kann“. Seit C# 8 können Interfaces außerdem Standardimplementierungen von Methoden enthalten.
Was bedeutet sealed in C#?
Eine sealed-Klasse kann nicht als Basisklasse verwendet werden; von ihr abzuleiten ist Fehler CS0509. string ist zum Beispiel sealed. An einer Methode verhindert sealed override, dass Klassen weiter unten in der Hierarchie sie erneut überschreiben (CS0239). Klassen zu versiegeln, die nicht für Vererbung entworfen wurden, ist ein vernünftiger Standard.
Wie rufe ich in C# die Methode der Basisklasse auf?
Mit base.MethodName(...) innerhalb der abgeleiteten Klasse, meist innerhalb der Überschreibung: public override string Describe() => base.Describe() + " with GPS";. Bei Konstruktoren wählst du mit : base(...) nach der Parameterliste, welcher Basiskonstruktor läuft.