CodeGym /Kurse /C# SELF /Das Konzept des Interface und seine Syntax

Das Konzept des Interface und seine Syntax

C# SELF
Level 23 , Lektion 0
Verfügbar

1. Was ist ein Interface?

Wenn eine abstrakte Klasse so etwas wie ein "Halbfertigprodukt" mit teilweiser Implementierung ist, dann ist ein Interface einfach nur eine Liste von Anforderungen: was ein bestimmtes Wesen (Klasse) können muss, damit es in einem bestimmten abstrakten Kontext verwendet werden kann.

In der Programmierung ist ein Interface wie ein "Vertrag" oder eine "Anforderungsliste" für das Verhalten eines Objekts. Es beschreibt eine Menge von öffentlichen Methoden, Properties, Indexern und Events, die eine Klasse, die dieses Interface implementiert, bereitstellen muss. Das Interface sagt: "Wenn du dich zum Beispiel IDriveable (fahrbar) nennen willst, dann sei so nett und stelle die Methoden Drive() und Stop() bereit."

Man kann sich ein Interface wie eine Anforderungsliste für einen Mitarbeiter vorstellen: Wenn du zum Beispiel "Köche" einstellen willst, steht im Interface: "Muss kochen, Gerichte servieren und Hände waschen können". Wie der konkrete Koch das macht, ist seine Sache. Hauptsache, er hält sich an den Vertrag.

Wichtig: Das Interface beschreibt nur was die Klasse bereitstellen muss, nicht wie das gemacht wird.

Kurz in OOP-Begriffen

  • Interface beschreibt das äußere "Aussehen" (API) eines Objekts: welche Aktionen es erlaubt.
  • Enthält keinen Zustand (keine Felder). Klassisch enthalten Interfaces auch keine Implementierung, aber in modernen C#-Versionen gibt es Ausnahmen (z.B. Default-Methoden), über die wir später noch sprechen.
  • Eine Klasse kann beliebig viele Interfaces implementieren (im Gegensatz zur Vererbung von Klassen!).

Analogie aus dem Leben

USB-Port — jeder weiß, dass man eine Maus, Tastatur, einen USB-Stick, Kopfhörer oder sogar eine Kaffeemaschine (ja, gibt's wirklich!) anschließen kann. Was in der "Maus" drin ist, ist egal, Hauptsache, sie hat einen USB-Anschluss und unterstützt das richtige Protokoll. Das ist ein Interface!

Wozu braucht man Interfaces?

  • Entkoppelt den Code. Der Code arbeitet mit dem Interface, nicht mit einer konkreten Klasse. Wir programmieren "auf Interface-Level".
  • Erlaubt Mehrfachvererbung von Verhalten: Eine Klasse kann mehrere Interfaces implementieren.
  • Standardisierung: Man kann universelle Mechanismen bauen: z.B. alle Objekte, die vergleichbar sind, implementieren das Interface IComparable.
  • Testbarkeit: Man kann die konkrete Implementierung im Test einfach durch einen Dummy ersetzen.
  • Pluginfähigkeit: Man kann neue "Module" hinzufügen, ohne bestehenden Code zu ändern.

2. Syntax zur Deklaration eines Interface

Und jetzt — ab in den Code! In C# deklariert man ein Interface mit dem Schlüsselwort interface, und Interface-Namen fangen üblicherweise mit I an:


// Interface-Deklaration
public interface IPrintable
{
    // Abstrakte Methode — Vertragsbestandteil
    void Print();

    // Man kann auch Properties deklarieren
    string Name { get; set; }
}

Merke dir: Methoden und Properties im Interface HABEN KEINE Implementierung (kein Methodenkörper, nur die Signatur — genau wie bei abstrakten Methoden).

Wichtige Eigenschaften eines Interface

  • Man kann keine Felder (Member-Variablen) im Interface deklarieren.
  • Alle Methoden, Properties, Events und Indexer sind standardmäßig public (und müssen es auch in der Implementierung sein).
  • Ein Interface kann keine Konstruktoren enthalten (weil es keinen Zustand hat).
  • Das Interface ist unabhängig davon, ob die implementierende Klasse abstrakt oder konkret ist.

Interface in Aktion

Lass uns das Interface in unsere Lern-App einbauen. Jetzt gibt es ein Interface "druckbar" (IPrintable), das von der Klasse Report und zum Beispiel einer neuen Klasse Invoice implementiert wird.

public interface IPrintable
{
    void Print();
    string Name { get; set; }
}

Jetzt definieren wir eine Klasse, die dieses Interface implementiert:

public class Report : IPrintable
{
    public string Name { get; set; }

    public Report(string name)
    {
        Name = name;
    }

    // Implementierung der Methode aus dem Interface
    public void Print()
    {
        Console.WriteLine($"Drucken des Berichts: {Name}");
    }
}

Und jetzt — eine ganz andere Klasse, aber mit demselben Interface:

public class Invoice : IPrintable
{
    public string Name { get; set; }

    public Invoice(string name)
    {
        Name = name;
    }

    public void Print()
    {
        Console.WriteLine($"Drucken der Rechnung: {Name}");
    }
}

Jetzt können wir eine Methode schreiben, die mit jedem druckbaren Objekt arbeitet:

public static void PrintAnything(IPrintable printable)
{
    printable.Print(); // Das war's! Egal ob Bericht oder Rechnung — Hauptsache, es kann drucken.
}

Und hier ein Beispiel für die Benutzung:

var report = new Report("Monatsbericht");
var invoice = new Invoice("Rechnung #12345");

PrintAnything(report);  // Drucken des Berichts: Monatsbericht
PrintAnything(invoice); // Drucken der Rechnung: Rechnung #12345

So machen Interfaces deinen Code universell, erweiterbar und einfach nice.

3. Implementierung von Interfaces

Ein Interface an eine Klasse anbinden

Ein Interface wird in einer Klasse mit Doppelpunkt implementiert (ja, wie bei Vererbung):

public class Ticket : IPrintable
{
    public string Name { get; set; }
    public void Print()
    {
        Console.WriteLine($"Drucken des Tickets: {Name}");
    }
}

Wichtig: Die Klasse muss alle Mitglieder des Interface implementieren. Die implementierten Mitglieder müssen public sein.

Wenn du auch nur ein Mitglied nicht implementierst:

public class BrokenTicket : IPrintable
{
    // Print() fehlt
    public string Name { get; set; }
}
// Compilerfehler: 'BrokenTicket' implementiert das Interface-Mitglied 'IPrintable.Print()' nicht

Mehrere Interfaces

Eine Klasse kann mehrere Interfaces durch Komma getrennt implementieren:

public interface IStorable
{
    void Store();
}

public class MultiPurposeDoc : IPrintable, IStorable
{
    public string Name { get; set; }
    public void Print()
    {
        Console.WriteLine("Dokument drucken");
    }

    public void Store()
    {
        Console.WriteLine("Dokument speichern");
    }
}

4. Wozu braucht man Interfaces?

Vielleicht denkst du jetzt: "Okay, die Syntax ist klar. Aber wozu brauche ich das im echten Leben, außer um mir das Leben in diesem Kurs schwerer zu machen?" Keine Sorge! Interfaces sind eines der meistgenutzten Werkzeuge in der professionellen Entwicklung.

Aufgabentrennung und lose Kopplung (Decoupling / Loose Coupling):

  • Stell dir vor, du entwickelst einen Musikplayer. Dem ist egal, woher die Musik kommt — von einer lokalen Datei, aus dem Internet oder von einer CD. Wichtig ist nur, dass die Musikquelle einen Audiostream liefern kann.
  • Du kannst ein Interface IAudioSource mit der Methode GetAudioStream() definieren.
  • Dann hast du Klassen wie FileAudioSource, InternetAudioSource, CDAudioSource, die dieses Interface implementieren.
  • Dein Player arbeitet mit IAudioSource, ohne den konkreten Typ zu kennen. Wenn morgen eine neue Quelle kommt, z.B. BluetoothAudioSource, musst du den Player-Code nicht ändern! Einfach eine neue Klasse schreiben, die IAudioSource implementiert. Das macht dein System viel flexibler und leichter erweiterbar. Das ist lose Kopplung – Komponenten hängen von Abstraktionen (Interfaces) ab, nicht von konkreten Implementierungen.

Polymorphismus und einheitliche Behandlung:

Wie wir im Beispiel mit PrintAnything gesehen haben, kannst du eine Sammlung von Objekten verschiedener Typen haben, die aber durch ein gemeinsames Verhalten im Interface verbunden sind. Du kannst bei allen denselben Methodennamen (Print()) aufrufen, ohne zu wissen, was genau vor dir steht — Bericht, Rechnung oder Ticket. So kannst du sehr kompakten und universellen Code schreiben.

Testing (Unit Testing):

Das ist wahrscheinlich einer der wichtigsten Anwendungsfälle für Interfaces. Wenn du eine Komponente deiner App testest, braucht sie oft andere Komponenten zum Arbeiten (z.B. eine Klasse, die Daten speichert, hängt von einer Klasse ab, die mit der Datenbank arbeitet).

Statt die echte Klasse DatabaseSaver zu übergeben (die eine echte Datenbank zum Testen braucht!), kannst du ein "Fake"-Objekt (oder "Mock") übergeben, das einfach das Interface IDataSaver implementiert. Dieses "Mock"-Objekt tut nur so, als würde es speichern, ohne eine echte Datenbank zu benutzen. So kannst du Komponenten isoliert, schnell und ohne externe Abhängigkeiten testen.

API- und Framework-Entwicklung:

Wenn du eine Bibliothek oder ein Framework baust, willst du Entwicklern "Erweiterungspunkte" geben. Interfaces sind dafür perfekt. Du kannst sagen: "Wenn dein Component mit meinem System arbeiten soll, implementiere dieses Interface." Die Standardbibliotheken von .NET sind voll von Interfaces (z.B. IEnumerable<T>, IDisposable, IComparable<T>) – sie definieren Verträge für die gängigsten Szenarien.

Direktes Programmieren auf Interface-Level (Programming to an Interface):

Erfahrene Entwickler sagen oft: "Programmiere auf Interface-Level, nicht auf Implementierungs-Level." Das heißt: Wenn du den Typ einer Variable oder eines Methodenparameters festlegst, nimm lieber das Interface (IDriveable) statt einer konkreten Klasse (Car). So wird dein Code flexibler und weniger abhängig von Implementierungsdetails, und du kannst eine Implementierung leicht durch eine andere ersetzen.

5. Typische Fehler im Umgang mit Interfaces

Fehler Nr. 1: Versuch, ein Interface zu instanziieren.
Du kannst schreiben Cat murzik = new Cat("Murzik", 3);, weil Cat eine konkrete Klasse ist. Aber du kannst nicht schreiben ITalkable talker = new ITalkable();. Ein Interface ist nur ein Vertrag, eine Vorlage. Es hat keine Implementierung und kann nicht direkt erzeugt werden. Das ist wie ein Bauplan, aber kein fertiges Haus.

Fehler Nr. 2: Nicht alle Interface-Mitglieder implementiert.
Wenn du angibst, dass deine Klasse ein Interface implementiert, z.B. IMyInterface, dann musst du alle Methoden davon implementieren. Auch nur eine vergessene Methode führt zu einem Compilerfehler: MyClass implementiert IMyInterface.TheMissingMethod() nicht.

Fehler Nr. 3: Falsche Zugriffsmodifikatoren bei der Implementierung.
Methoden im Interface sind implizit public, und bei der Implementierung müssen sie auch public sein. Wenn du versuchst, die Methode private oder protected zu machen, gibt's einen Compilerfehler. Versprochen ist versprochen — also offen implementieren.

Fehler Nr. 4: Versuch, Felder oder Konstruktoren ins Interface zu packen.
Interfaces beschreiben Verhalten, keinen Zustand. Deshalb darfst du keine Felder oder Konstruktoren reinpacken. Wenn du es versuchst — Compilerfehler. Nur Properties sind erlaubt, und auch nur als Beschreibung von Getter/Setter.

Fehler Nr. 5: Verwechslung von override und Interface-Implementierung.
Das Schlüsselwort override wird zum Überschreiben von Methoden der Basisklasse verwendet. Bei der Interface-Implementierung brauchst du das aber nicht — du schreibst einfach eine public-Methode mit der passenden Signatur. Das ist ein wichtiger Unterschied, den man leicht übersieht.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION