1. Einführung
In dieser Vorlesung schauen wir uns an, wie die Vererbungssyntax in C# funktioniert: Wir lernen, wie man abgeleitete Klassen deklariert, was das Schlüsselwort base ist und wie man damit auf die Elternklasse zugreift. Das macht dein Leben beim Coden nicht nur einfacher, sondern sorgt auch für eleganteren Code (und manchmal kannst du sogar länger schlafen – weil du keine Copy-Paste-Fehler mehr fixen musst).
Hier ein einfaches Beispiel aus dem Alltag: Wir haben eine Klasse Vehicle (Fahrzeug) – das ist unser Basis-Blueprint. Alle Fahrzeuge können fahren, haben eine Farbe, einen Hersteller usw.:
public class Vehicle
{
public string Brand { get; set; }
public string Color { get; set; }
public void Drive()
{
Console.WriteLine("Los geht's!");
}
}
Und jetzt wollen wir eine neue Art von Fahrzeug hinzufügen – ein Auto. Ein Auto ist immer noch ein Fahrzeug, hat aber Besonderheiten (z.B. die Anzahl der Türen). Es wäre komisch, alles über Farbe und Hersteller nochmal zu schreiben, das ist ja schon definiert!
Vererbung erlaubt uns zu sagen: "Ein Auto ist ein Fahrzeug, nur mit ein paar Extra-Features."
2. Wie deklariert man eine Kindklasse
In C# benutzt man für die Erstellung einer abgeleiteten Klasse einen Doppelpunkt :. Nach dem Klassennamen gibt man an, von welcher Klasse geerbt wird.
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
}
Lies das so: Die Klasse Car erbt alle Felder und Methoden von Vehicle und fügt eigene Features hinzu – zum Beispiel die Anzahl der Türen.
Achtung: In C# gibt es nur einfache Klassenvererbung (d.h. eine Klasse kann nur EINE Elternklasse haben). Aber Interfaces kannst du so viele implementieren, wie du willst! (Dazu später mehr).
Wie funktioniert das?
Wenn du schreibst:
Car myCar = new Car();
...dann hat dein Objekt myCar Zugriff auf alle Felder/Methoden von Vehicle und auf seine eigenen Properties. Beispiel:
myCar.Brand = "Toyota";
myCar.Color = "Blau";
myCar.NumberOfDoors = 4;
myCar.Drive(); // Geerbte Methode
Realistisches Szenario:
In unserer "wachsenden" App könnte man ein Menü für Fahrzeuge bauen, wo der User den Typ auswählt und das Programm alle Eigenschaften anzeigt. Wenn du einen neuen Fahrzeugtyp hinzufügst, musst du nur minimalen Code schreiben!
3. Vererbung von Konstruktoren
Wenn wir Objekte von abgeleiteten Klassen erstellen, wollen wir manchmal die Properties der Basisklasse direkt im Konstruktor initialisieren. Zum Beispiel, damit man Marke und Farbe garantiert nicht vergisst:
public class Vehicle
{
public string Brand { get; set; }
public string Color { get; set; }
public Vehicle(string brand, string color)
{
Brand = brand;
Color = color;
}
}
Jetzt kann (und sollte) man im Konstruktor der abgeleiteten Klasse den Konstruktor der Basisklasse aufrufen! Dafür benutzt man das Schlüsselwort base:
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public Car(string brand, string color, int numberOfDoors)
: base(brand, color) // ruft Vehicle-Konstruktor auf!
{
NumberOfDoors = numberOfDoors;
}
}
Wie sieht das in der Praxis aus?
Car bmw = new Car("BMW", "Schwarz", 4);
Console.WriteLine($"{bmw.Brand}, {bmw.Color}, Türen: {bmw.NumberOfDoors}");
bmw.Drive(); // Funktioniert immer noch!
Das heißt, bmw ist gleichzeitig ein Auto und ein Fahrzeug. Es "erinnert" sich an seine "Eltern".
4. Unterschied zwischen base und this
In C# gibt es zwei Schlüsselwörter für den Zugriff auf Klassenmitglieder: this und base.
- this – Zugriff auf das aktuelle Objekt (z.B. sein eigenes Feld oder Methode).
- base – Zugriff auf ein Mitglied der Basisklasse (also auf den "Elternteil").
Wann benutzt man base? Stell dir vor, du willst die Logik einer Methode aus der Basisklasse erweitern oder einfach die Basisklasse aus der abgeleiteten initialisieren. Zum Beispiel:
public class Bicycle : Vehicle
{
public int NumberOfGears { get; set; }
public Bicycle(string brand, string color, int gears)
: base(brand, color) // ruft Basisklassen-Konstruktor auf
{
NumberOfGears = gears;
}
public void ShowInfo()
{
// Benutzt Properties aus der Basisklasse!
Console.WriteLine($"{Brand} ({Color}), Gänge: {NumberOfGears}");
}
}
5. Schlüsselwort base in Methoden
base ist nicht nur für Konstruktoren! Manchmal willst du das Verhalten einer Methode der Basisklasse ändern, aber trotzdem einen Teil ihrer Logik nutzen.
Angenommen, wir wollen, dass die Methode Drive in der Klasse Car zusätzlich ausgibt, welches Auto wir fahren:
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public Car(string brand, string color, int numberOfDoors)
: base(brand, color)
{
NumberOfDoors = numberOfDoors;
}
public void DriveCar()
{
base.Drive(); // ruft Basis-Implementierung auf
Console.WriteLine($"Wir fahren {Brand} mit {NumberOfDoors} Türen!");
}
}
Erklärung:
Der Aufruf base.Drive(); ist wie: "Papa, erzähl du erst deinen Teil der Geschichte, dann ergänze ich Details!". Beim Aufruf von DriveCar() wird also ausgegeben:
Los geht's!
Wir fahren BMW mit 4 Türen!
Über das Überschreiben von Methoden (override) reden wir in den nächsten Vorlesungen :P
6. Vererbungsschema
Lass uns die Beziehungen zwischen den Klassen visualisieren. Hier eine einfache Skizze (UML ist morgens eh zu heavy!):
Vehicle (Basis)
/ \
Car Bicycle
- Alle Pfeile gehen VOM abgeleiteten ZUR Basisklasse.
- Alle Kindklassen haben Zugriff auf public/protected Mitglieder der Basisklasse.
Unterschiede zwischen public, protected und private
Kleine Erinnerung, damit es keine Verwirrung gibt.
In der abgeleiteten Klasse sind sichtbar:
| Modifikator | Sichtbarkeit im Kind |
|---|---|
| public | Ja |
| protected | Ja |
| private | Nein |
Häufiger Fehler:
Versuchen, auf ein private-Feld aus der Kindklasse zuzugreifen:
public class Vehicle
{
private string secret = "Niemand wird es erfahren!";
}
public class Car : Vehicle
{
public void RevealSecret()
{
Console.WriteLine(secret); // Fehler! Nicht sichtbar.
}
}
Vergleich: Basis vs. abgeleitete Klasse
| Vehicle | Car (abgeleitet) | |
|---|---|---|
| Brand | vorhanden | erbt |
| Color | vorhanden | erbt |
| Drive() | vorhanden | erbt (oder erweitert) |
| NumberOfDoors | nein | eigenes (unique Property) |
7. Beispiel
Wenn wir unsere kleine App „Fahrzeugverwaltung“ weiterentwickeln, könnte sie so aussehen:
public class Vehicle
{
public string Brand { get; set; }
public string Color { get; set; }
public Vehicle(string brand, string color)
{
Brand = brand;
Color = color;
}
public void Drive()
{
Console.WriteLine($"{Brand} {Color} fährt los!");
}
}
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public Car(string brand, string color, int numberOfDoors)
: base(brand, color)
{
NumberOfDoors = numberOfDoors;
}
public void ShowCar()
{
Console.WriteLine($"Marke: {Brand}, Farbe: {Color}, Türen: {NumberOfDoors}");
}
}
public class Bicycle : Vehicle
{
public int NumberOfGears { get; set; }
public Bicycle(string brand, string color, int numberOfGears)
: base(brand, color)
{
NumberOfGears = numberOfGears;
}
}
Im Main-Method kannst du eine Liste von Fahrzeugen erstellen und sie unterschiedlich beschreiben:
Car ford = new Car("Ford", "Rot", 4);
ford.ShowCar();
ford.Drive();
Bicycle trek = new Bicycle("Trek", "Grün", 21);
trek.Drive();
8. Fehler, Besonderheiten und ein bisschen Humor
Sehr häufiger Fehler – den Basisklassen-Konstruktor im Kind nicht aufzurufen. Wenn Vehicle keinen parameterlosen Konstruktor hat und du base(...) nicht aufrufst, gibt's einen Compilerfehler: Der Compiler meckert, dass er die Basis nicht initialisieren kann. Das ist wie ein zweites Stockwerk ohne das erste bauen zu wollen. Geht nicht. Du musst die Daten nach oben durchreichen.
Zweiter Fehler: vergessen, dass Felder/Methoden der Basisklasse mit dem Modifier private in Kindklassen NICHT SICHTBAR sind. Wenn du was für die "Kinder" zugänglich machen willst, nimm protected (mehr dazu in den nächsten Vorlesungen).
Und das Wichtigste: Vererbung ist praktisch, aber bau keine zu verschachtelten Hierarchien. Im echten Leben, wenn deine Vererbungskette mehr als zwei-drei Ebenen hat, läuft wahrscheinlich was schief. Es ist superleicht, sich zu verirren, wer von wem erbt und warum das alles überhaupt so ist.
GO TO FULL VERSION