CodeGym /Kurse /C# SELF /Einführung in die Vererbung

Einführung in die Vererbung

C# SELF
Level 12, Lektion 2
Verfügbar

1. Einführung

Das heutige Thema wirkt vielleicht erstmal etwas abstrakt, aber glaub mir, ohne das geht später gar nichts mehr. Wir reden über Vererbung in der Programmierung. Heute schauen wir nur kurz rein, später gehen wir dann richtig ins Detail.

Stell dir vor, du schreibst ein Programm zur Verwaltung von Tieren im Zoo. Du hast verschiedene Tierarten: Löwen, Tiger, Elefanten, Papageien. Alle sind Tiere. Aber sie haben auch ihre eigenen Besonderheiten:

  • Der Löwe hat eine Mähne.
  • Der Tiger ist gestreift.
  • Der Elefant ist sehr groß und hat einen Rüssel.
  • Der Papagei kann sprechen.

Wenn wir jedes Tier einzeln beschreiben würden, wäre der Code ziemlich ähnlich:


// Löwe
public class Lion
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Species { get; set; } = "Löwe"; // Immer "Löwe"
    public void Eat() { Console.WriteLine("Löwe isst Fleisch."); }
    public void Sleep() { Console.WriteLine("Löwe schläft."); }
    public void Roar() { Console.WriteLine("Löwe brüllt!"); }
    public string ManeColor { get; set; } // Besonderheit des Löwen - Mähne
}

// Tiger
public class Tiger
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Species { get; set; } = "Tiger"; // Immer "Tiger"
    public void Eat() { Console.WriteLine("Tiger isst Fleisch."); }
    public void Sleep() { Console.WriteLine("Tiger schläft."); }
    public void Stride() { Console.WriteLine("Tiger schleicht."); }
    public string StripePattern { get; set; } // Besonderheit des Tigers - Streifenmuster
}

Siehst du, wie viel doppelter Code da ist? Name, Age, Eat(), Sleep() – das ist doch für alle Tiere gleich! Und was, wenn wir 100 Tierarten hätten? Horror!

Genau hier hilft uns die Vererbung.

2. Was ist Klassenvererbung in C#?

Vererbung ist eines der Grundprinzipien der objektorientierten Programmierung (OOP). Damit kannst du neue Klassen auf Basis bereits existierender Klassen erstellen. Die neue Klasse (Kindklasse, abgeleitete Klasse) bekommt (erbt) alle Eigenschaften (Felder) und das Verhalten (Methoden) ihrer Elternklasse (Basisklasse, Elternklasse).

Das ist wie im echten Leben: Du erbst bestimmte Eigenschaften von deinen Eltern, hast aber auch deine eigenen Besonderheiten.

In C# wird Vererbung mit einem Doppelpunkt : nach dem Namen der Kindklasse geschrieben:


            // Elternklasse (Basisklasse) - Animal
public class Animal
{
    public string Name { get; set; } // Name des Tieres
    public int Age { get; set; }     // Alter des Tieres

    public void Eat()
    {
        Console.WriteLine($"{Name} isst.");
    }

    public void Sleep()
    {
        Console.WriteLine($"{Name} schläft.");
    }
}

// Kindklasse (abgeleitete Klasse) - Lion erbt von Animal
public class Lion : Animal // <-- Das ist Vererbung!
{
    public string ManeColor { get; set; } // Einzigartige Eigenschaft des Löwen

    public void Roar() // Einzigartiges Verhalten des Löwen
    {
        Console.WriteLine($"{Name} brüllt: RRRRRRR!");
    }
}

// Noch eine Kindklasse - Tiger erbt von Animal
public class Tiger : Animal
{
    public string StripePattern { get; set; } // Einzigartige Eigenschaft des Tigers

    public void Stride() // Einzigartiges Verhalten des Tigers
    {
        Console.WriteLine($"{Name} schleicht lautlos.");
    }
}

Jetzt, wenn wir ein Objekt Lion oder Tiger erstellen, haben sie automatisch die Eigenschaften Name und Age sowie die Methoden Eat() und Sleep(), weil sie diese von Animal geerbt haben.


class Program
{
    static void Main(string[] args)
    {
        Lion simba = new Lion();
        simba.Name = "Simba";
        simba.Age = 5;
        simba.ManeColor = "Gold";

        simba.Eat();   // Methode geerbt von Animal
        simba.Sleep(); // Methode geerbt von Animal
        simba.Roar();  // Eigene Methode von Lion

        Console.WriteLine($"{simba.Name} - Alter: {simba.Age}, Mähnenfarbe: {simba.ManeColor}");

        Tiger shereKhan = new Tiger();
        shereKhan.Name = "Shere Khan";
        shereKhan.Age = 7;
        shereKhan.StripePattern = "Klassisch";

        shereKhan.Eat();   // Methode geerbt von Animal
        shereKhan.Sleep(); // Methode geerbt von Animal
        shereKhan.Stride(); // Eigene Methode von Tiger

        Console.WriteLine($"{shereKhan.Name} - Alter: {shereKhan.Age}, Muster: {shereKhan.StripePattern}");
    }
}

Wichtige Ideen der Vererbung:

  1. Code-Wiederverwendung (Code Reusability): Du vermeidest doppelten Code, indem du die gemeinsame Logik in die Basisklasse packst.
  2. Hierarchie (Hierarchy): Es entsteht eine logische Struktur "allgemein-spezifisch" (Animal → Lion/Tiger).
  3. Polymorphismus (Polymorphism): (Nur kurz erwähnt, mehr dazu später) Du kannst mit Objekten der Kindklassen über Referenzen auf die Basisklasse arbeiten. Zum Beispiel kannst du eine Liste von Animal haben und dort Löwen und Tiger reinpacken und dann bei allen die Methode Eat() aufrufen.

3. Aufruf von Basisklassen-Konstruktoren (base)

Wenn du ein Objekt einer Kindklasse erstellst (zum Beispiel new Lion()), ruft C# automatisch zuerst den Konstruktor der Basisklasse (bei uns Animal) vor dem Konstruktor der Kindklasse auf. Das stellt sicher, dass der Basisteil des Objekts richtig initialisiert wird.

Manchmal musst du Parameter an den Konstruktor der Basisklasse übergeben. Dafür benutzt du das Schlüsselwort base nach der Signatur des Kindklassen-Konstruktors.


public class Animal
{
    public string Name { get; set; }
    public int Age { get; set; }

    // Konstruktor der Basisklasse
    public Animal(string name, int age)
    {
        Name = name;
        Age = age;
        Console.WriteLine($"Konstruktor Animal: Tier {Name} erstellt, Alter {Age}.");
    }

    public void Eat()
    {
        Console.WriteLine($"{Name} isst.");
    }

    public void Sleep()
    {
        Console.WriteLine($"{Name} schläft.");
    }
}

public class Lion : Animal
{
    public string ManeColor { get; set; }

    // Konstruktor von Lion, der den Konstruktor der Basisklasse Animal aufruft
    public Lion(string name, int age, string maneColor) : base(name, age) // <-- Hier ist 'base'!
    {
        ManeColor = maneColor;
        Console.WriteLine($"Konstruktor Lion: Mähnenfarbe {ManeColor}.");
    }

    public void Roar()
    {
        Console.WriteLine($"{Name} brüllt: RRRRRRR!");
    }
}

            class Program
{
    static void Main(string[] args)
    {
        // Beim Erstellen von Lion wird zuerst der Konstruktor von Animal, dann von Lion aufgerufen    
        Lion simba = new Lion("Simba", 5, "Gold");
        simba.Roar();
    }
}

Was passiert, wenn du new Lion("Simba", 5, "Gold") ausführst:

  1. Der Konstruktor Lion(string name, int age, string maneColor) wird aufgerufen.
  2. Durch : base(name, age) wird der Konstruktor Animal(string name, int age) aufgerufen.
  3. Der Code des Animal-Konstruktors läuft und du siehst Konstruktor Animal: Tier Simba erstellt, Alter 5.
  4. Danach geht es zurück in den Konstruktor von Lion.
  5. Der Code des Lion-Konstruktors läuft und du siehst Konstruktor Lion: Mähnenfarbe Gold.

Das ist super wichtig: Die Basisklasse wird immer zuerst initialisiert!

4. Operatoren is und as

In der Programmierwelt mit C# wirst du oft Situationen haben, in denen du den Typ eines Objekts prüfen oder es in einen anderen Typ umwandeln willst. Hier helfen die Operatoren is und as. Sie sind besonders nützlich bei Vererbung und Polymorphismus, worüber wir später noch mehr reden, aber die Basics kannst du jetzt schon verstehen.

Operator is (Typprüfung)

Der Operator is prüft, ob ein Objekt mit einem bestimmten Typ kompatibel ist. Er gibt true zurück, wenn das Objekt in den angegebenen Typ konvertiert werden kann, und false sonst.

Syntax: Ausdruck is Typ


static void AnalyzeObject(object obj)
{
    if (obj is string)
    {
        Console.WriteLine("Das ist ein String!");
    }
    else if (obj is int)
    {
        Console.WriteLine("Das ist eine ganze Zahl!");
    }
    else
    {
        Console.WriteLine("Das ist etwas anderes.");
    }
}

static void Main()
{
    AnalyzeObject("Hallo, Welt!");      // Das ist ein String!
    AnalyzeObject(123);                 // Das ist eine ganze Zahl!
    AnalyzeObject(3.14);                // Das ist etwas anderes.
    AnalyzeObject(new int[] { 1, 2 });  // Das ist etwas anderes.
}

is mit Pattern Matching:

Modernes C# erlaubt es, is nicht nur zur Typprüfung zu nutzen, sondern auch gleich eine Variable dieses Typs zu erstellen, wenn der Typ passt. Das nennt sich "Pattern Matching" und macht den Code viel kürzer.


static void AnalyzeObjectWithPatternMatching(object obj)
{
    if (obj is string message) // Wenn obj ein String ist, dann in Variable message speichern
    {
        Console.WriteLine($"Das ist ein String, seine Länge: {message.Length}");
    }
    else if (obj is int number) // Wenn obj ein int ist, dann in Variable number speichern
    {
        Console.WriteLine($"Das ist eine Zahl, mal 2: {number * 2}");
    }
    else
    {
        Console.WriteLine("Typ nicht erkannt.");
    }
}

static void Main()
{
    AnalyzeObjectWithPatternMatching("Beispiel"); // Das ist ein String, seine Länge: 8
    AnalyzeObjectWithPatternMatching(50);       // Das ist eine Zahl, mal 2: 100
    AnalyzeObjectWithPatternMatching(new object()); // Typ nicht erkannt.
}

Dieser Ansatz mit is und Pattern Matching ist viel besser als das klassische Casten mit nachfolgender null-Prüfung, weil er sicherer und lesbarer ist.

Operator as (sicheres Casten)

Der Operator as versucht, ein Objekt in den angegebenen Typ zu konvertieren. Wenn das klappt, bekommst du das Objekt im neuen Typ. Wenn nicht (also wenn das Objekt nicht kompatibel ist), gibt as null zurück, statt eine Exception zu werfen. Das macht as zu einem "sicheren" Cast-Operator.

Syntax: Ausdruck as Typ


static void ProcessData(object data)
{
    string text = data as string; // Versuchen, data zu string zu casten
    
    if (text != null) // Prüfen, ob das Casten geklappt hat
    {
        Console.WriteLine($"String-Verarbeitung: {text.ToUpper()}");
    }
    else
    {
        Console.WriteLine("Die Daten sind kein String.");
    }
}

static void Main()
{
    ProcessData("hello");      // String-Verarbeitung: HELLO
    ProcessData(123);          // Die Daten sind kein String.
    ProcessData(null);         // Die Daten sind kein String.
}

Wann as statt direktem Cast (Typ)Ausdruck verwenden:

  • Sicherheit: Wenn du nicht sicher bist, ob das Objekt wirklich vom richtigen Typ ist, hilft dir as, eine InvalidCastException zu vermeiden und stattdessen null zu bekommen, das du abfangen kannst.
  • Mit Referenztypen: as funktioniert nur mit Referenztypen (Klassen, Interfaces, Delegates, Arrays). Für Werttypen (z.B. int zu double) geht das nicht.

Wichtig: Wenn du sicher bist, dass das Objekt immer vom richtigen Typ ist, oder wenn du einen Werttyp casten willst, nutze das direkte Casten (Typ)Ausdruck. Wenn das Casten nicht klappt, gibt es eine Exception (InvalidCastException), was manchmal sogar gewollt ist, wenn ein falscher Typ ein Programmierfehler ist.

2
Aufgabe
C# SELF, Level 12, Lektion 2
Gesperrt
Erstellung einer Basisklasse und ihrer Nachfolger
Erstellung einer Basisklasse und ihrer Nachfolger
2
Aufgabe
C# SELF, Level 12, Lektion 2
Gesperrt
Konstruktoren in abgeleiteten Klassen
Konstruktoren in abgeleiteten Klassen
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION