CodeGym /Kurse /C# SELF /Vergleich von Interfaces und abstrakten Klassen

Vergleich von Interfaces und abstrakten Klassen

C# SELF
Level 24 , Lektion 2
Verfügbar

1. Kurz zu den klassischen Unterschieden

Wenn jemand sagt: „Ein Interface ist einfach nur eine Sammlung von Signaturen“, frag nach: „Mit welcher C#-Version arbeitest du?“ Ab C# 8 und neuer sind Interfaces viel mächtiger geworden. Zeit, sie mit abstrakten Klassen zu vergleichen – nicht nur nach den klassischen Eigenschaften, sondern auch mit allen neuen Features der .NET-Plattform.

Wenn wir ein paar Jahre zurückgehen – zu C# 7 – war alles einfach. Eine abstrakte Klasse kann Felder und teilweise implementierte Methoden definieren, ein Interface – nur Signaturen (Methoden, Properties, Events, Indexer).
Die Vererbung einer abstrakten Klasse läuft über die Beziehung "ist-ein" (Is-a), Interfaces ermöglichen Mehrfachvererbung von Verhalten ("kann", can-do).

Eigenschaft Abstrakte Klasse Interface (bis C# 8)
Beziehung is-a can-do
Vererbung Nur eine Mehrfach
Felder Kann enthalten Kann nicht
Methodenimplementierung Kann Kann nicht
Konstruktoren Kann Kann nicht
Zugriffsmodifikatoren Verschiedene (public, protected, ...) Nur implizit public

Wie du siehst, waren abstrakte Klassen früher die echten "großen Brüder" der Interfaces – mächtiger und flexibler. Aber alles ändert sich!

2. Interfaces mit Standardimplementierung

Mit C# 8 (und erst recht in C# 14 und .NET 9) haben Interfaces eine neue Superkraft bekommen – Methoden mit Standardimplementierung, die man "Default Interface Methods" (DIM) nennt.

Wie sieht das aus?


public interface IAnimal
{
    void SagHallo();

    // Methode mit Standardimplementierung!
    void Laufen()
    {
        Console.WriteLine("Ich gehe...");
    }
}

Wow! Plötzlich kann ein Interface Methoden implementieren. Und zwar beliebig viele. Aber es gibt einen Haken: Solche Methoden müssen explizit mit Body deklariert werden, alles andere (Felder, private Methoden, Konstruktoren) – weiterhin nicht erlaubt.

3. Moderne Interface-Features

Neue Features, die ein moderner .NET-Entwickler kennen sollte:

  • Methoden mit Standardimplementierung.
  • Private Methoden im Interface (nur für Hilfszwecke, nur für andere Methoden im selben Interface sichtbar).
  • Statische Methoden (ab C# 8).
  • Properties mit Standardimplementierung.
  • Statische Felder (ab C# 14 – "static interface members").
  • Abstrakte statische Member ("abstract static members" – ja, jetzt kann ein Interface bestimmte statische Methoden verlangen!).

Beispiel für ein modernes Interface:


public interface ILogger
{
    static int LoggerAnzahl { get; set; } // C# 14

    void Log(string nachricht); // Signatur (Vertrag)

    // Standardimplementierung
    void LogWarnung(string warnung)
    {
        Log("[WARNUNG]: " + warnung);
    }
    
    // Private Hilfsmethode im Interface (C# 8+)
    private void FormatiereUndLogge(string level, string msg)
    {
        Log($"{level}: {msg}");
    }

    // Statische Methode im Interface (C# 8+)
    static void DruckeLoggerInfo()
    {
        Console.WriteLine("Interface ILogger – dein bester Helfer!");
    }
}

Stell dir vor – früher war das einfach unmöglich, das ist als würde jemand einer Katze erlauben, Serverwächter zu sein.

4. Abstrakte Klassen: Was ist neu?

Abstrakte Klassen... wie soll ich sagen... haben sich im letzten Jahrzehnt nicht groß weiterentwickelt. Sie können immer noch:

  • Felder (auch private, protected und statische).
  • Implementierte und abstrakte Methoden.
  • Konstruktoren (ja, abstrakte Klassen können Initialisierungslogik haben).
  • Properties, Events, Indexer.
  • Statische und Instanz-Member.

Beispiel für eine abstrakte Klasse:


public abstract class Tier
{
    public string Name { get; set; }

    public abstract void Sprechen();

    public virtual void Laufen()
    {
        Console.WriteLine($"{Name} läuft auf Pfoten!");
    }

    protected void Fressen()
    {
        Console.WriteLine($"{Name} frisst Futter.");
    }
}

Eine abstrakte Klasse bleibt weiterhin ein super Ort, um gemeinsame Logik, Zustand und Verhalten für eine Klassenhierarchie zu speichern.

5. Moderner Vergleich: Tabelle mit neuen Features

Eigenschaft Abstrakte Klasse Interface (C# 14+, .NET 9)
Beziehung is-a (ist-ein) can-do (kann)
Vererbung Nur eine Mehrfach
Felder Ja, beliebige Nur statische* (C# 14+)
Konstruktoren Ja Nein
Methodenimplementierung Ja (virtual/abstract) Ja (default, static, abstract static)
Properties mit Implementierung Ja Ja (Standardimplementierung)
Private Member Ja Ja (nur Methoden, C# 8+)
Statische Member Ja Ja (C# 8+, mit Einschränkungen)
Statische Felder Ja Ja * (C# 14+)
Zugriffsmodifikatoren Beliebig Standardmäßig public oder private

* – In Interfaces werden statische Felder meist nur in Spezialfällen genutzt, und das ist ein ganz neues Sprachfeature.

6. Wann Interface, wann abstrakte Klasse: moderne Empfehlungen

Interfaces (jetzt auch mit Standardimplementierung) sind das Tool für Verträge zwischen Komponenten. Ihr Hauptmerkmal: Mehrfachheit. Deine Klasse kann beliebig viele Interfaces implementieren – sie wird zum Allrounder.

Abstrakte Klasse bleibt deine Wahl, wenn:

  • Gemeinsamer Zustand (Felder), Logik und Verhalten von anderen Klassen geerbt werden sollen.
  • Du Standardlogik brauchst, die überschreibbar ist (nutze virtual).
  • Du Initialisierung zentralisieren willst (Konstruktor).

In echten Projekten sieht man oft dieses Muster: "Reine Verträge" werden als Interfaces formuliert, und wenn gemeinsamer Code oder Infrastruktur für Subklassen gebraucht wird – nimmt man eine abstrakte Basisklasse.


.                     ┌────────────────────────┐
                      │      Interface         │
                      │  (Vertrag: was kann)   │
                      └─────────┬──────────────┘
                                │
                 ┌──────────────┼──────────────┐
                 │              │              │
           Implementierung 1   Implementierung 2 ...  Implementierung N
             MeinLogger     CloudLogger     FileLogger
    
        (kann mit Vererbung einer abstrakten Klasse kombiniert werden)
Schema: Wo und was verwenden

7. Szenarien – wann was gewinnt

Mehrfachimplementierung:
Angenommen, du hast ein Interface IFahrbar und eine abstrakte Klasse Fahrzeug. Jetzt kann die Klasse Auto die Basis Fahrzeug erben und gleichzeitig mehrere Interfaces implementieren (IFahrbar, IReparierbar, IVersicherbar). Hättest du eine abstrakte Klasse Reparierbar, müsstest du dich entscheiden – entweder Fahrzeug oder Reparierbar! Interfaces gewinnen hier klar.

Gemeinsame Logik und Zustand:
Angenommen, jedes "Fahrzeug" hat ein Feld "Nummer". Das sollte ein Feld der abstrakten Klasse sein. Im Interface sind Felder (außer statischen) nicht erlaubt.

API-Evolution:
Einer der revolutionären Punkte bei Default Interface Methods – jetzt kann man Interfaces weiterentwickeln, ohne bestehende Nutzer zu brechen.
Zum Beispiel: Du fügst dem Interface eine neue Methode mit Standardimplementierung hinzu – alles läuft, alle alten Implementierungen funktionieren weiter! Früher war das schmerzhaft (oder, wenn man den Schmerz vergisst, einfach unmöglich).

8. Praxisbeispiele

In unserer Lern-App taucht nach und nach Logging auf. Lass uns ein eigenes Interface ILogger mit Standardimplementierung bauen:


public interface ILogger
{
    void Log(string nachricht);

    // Standardimplementierung für alle Implementierungen des Interfaces!
    void LogInfo(string info)
    {
        Log("[INFO] " + info);
    }

    // Statische Methode im Interface
    static void DruckeHilfe()
    {
        Console.WriteLine("Nutze ILogger zum Loggen von Events");
    }
}

public class KonsolenLogger : ILogger
{
    public void Log(string nachricht)
    {
        Console.WriteLine(nachricht);
    }
}

// Irgendwo im Code:
ILogger logger = new KonsolenLogger();
logger.LogInfo("System gestartet!"); // funktioniert dank default-Implementierung

// Aufruf der statischen Interface-Methode
ILogger.DruckeHilfe();

Wenn wir dem Interface eine neue Methode mit Standardimplementierung hinzufügen, bekommen alle bestehenden Implementierungen (z.B. KonsolenLogger) diese Methode automatisch – kein Stress, kein kaputter Code.

9. Fehler und Besonderheiten: Praxis und Stolpersteine

Man sollte wissen, dass nicht alles so rosig ist, wie es scheint. Zum Beispiel: Wenn dein Interface eine default-Implementierung hat, aber der Nutzer das Objekt über den Klassentyp und nicht über das Interface anspricht, ist die default-Implementierung nur über das Interface verfügbar.


KonsolenLogger log = new KonsolenLogger();
log.LogInfo("Hallo"); // Kompiliert nicht: LogInfo ist in der Klasse nicht definiert!

ILogger log2 = log;
log2.LogInfo("Hallo"); // Alles gut!

Das erinnert an explizite Interface-Implementierung. Manchmal ist dieses Verhalten praktisch, um "überflüssiges" API zu verstecken, manchmal überrascht es Anfänger.

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