CodeGym /Kurse /C# SELF /Interfaces in der Standardbibliothek von .NET

Interfaces in der Standardbibliothek von .NET

C# SELF
Level 24, Lektion 3
Verfügbar

1. Klassifikation der wichtigsten Interfaces

Falls du denkst, Interfaces sind nur Spielzeug für Architekten und "Clean Code"-Fans und man kann im echten Leben darauf verzichten, muss ich dich enttäuschen: Jedes ernsthafte C#-Projekt taucht tief in Interfaces ein. Warum? Weil praktisch jeder Teil der .NET-Standardbibliothek über Interfaces läuft! Ohne sie kannst du weder mit Collections arbeiten, noch Dateien lesen oder sogar Collections mit LINQ filtern.

Interfaces sind das Fundament von Polymorphie und Erweiterbarkeit in .NET und sie bestimmen, wie die ganzen "Bausteine" des Frameworks zusammenpassen.

Die .NET-Standardbibliothek ist vollgestopft mit Interfaces. Damit du darin nicht untergehst, schlage ich folgende (natürlich unvollständige – das Leben reicht nicht, um alles zu erfassen!) Klassifikation vor:

Kategorie Interfaces Wofür braucht man sie?
Collections
IEnumerable
,
IEnumerator
,
IList
,
ICollection
,
IDictionary
Durchlaufen, Ändern, Zugriff per Index, Arbeit mit Key-Value-Paaren
Ressourcenmanagement
IDisposable
Freigabe von Ressourcen (Dateien, Verbindungen, Streams)
Vergleiche
IComparable
,
IComparer
,
IEquatable
Sortierung, Vergleich, Einzigartigkeit von Objekten
Serialisierung
ISerializable
Objekte in einen Bytestrom verwandeln (und zurück)
LINQ und Abfragen
IQueryable
,
IQueryProvider
Unterstützung komplexer Abfragen (z.B. für Datenbanken)
Asynchronität
IAsyncEnumerable
,
IAsyncDisposable
Asynchrones Durchlaufen und Freigeben von Ressourcen
Events und Benachrichtigungen
INotifyPropertyChanged
,
INotifyCollectionChanged
Reaktion auf Änderungen von Properties/Collections
Datum und Zeit
IFormattable
Custom String-Formatierung
Datenstrukturen
IStructuralComparable
,
IStructuralEquatable
Tiefes Vergleichen von Collections und Tuples
Streams/I/O
IStream
,
IAsyncDisposable
,
IObserver
,
IObservable
Arbeit mit Streams, Push/Pull-Benachrichtigungen

Wichtig! Viele der oben genannten Interfaces haben Typ-Parameter: List<string>. Meistens werden sie in Collections und für Collection-Interfaces verwendet Int[] → List<int>. Mehr dazu gibt's in ein paar Levels, wenn wir uns Collections genauer anschauen.

2. Collection-Interfaces

IEnumerable und IEnumerator – dein Ticket für foreach

Fast jede Collection in .NET implementiert das Interface IEnumerable oder sogar die generische Version IEnumerable<T>. Genau dieses Interface macht den magischen foreach-Syntax möglich:

List<int> zahlen = new List<int> { 1, 2, 3 };
foreach (int n in zahlen) // funktioniert, weil List<int> IEnumerable<int> implementiert
{
    Console.WriteLine(n);
}

So sieht das Interface in der minimalistischen Version aus:

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

IEnumerator ist das Interface für den eigentlichen "Durchläufer", der durch deine Collection läuft:

public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Reset();
}

In echten Aufgaben wirst du sehr oft Parameter übergeben und Werte vom Typ IEnumerable<T> zurückgeben. Zum Beispiel eine Methode, die alle geraden Zahlen aus einem Array zurückgibt:

public IEnumerable<int> GibGeradeZahlen(int[] array)
{
    foreach (var x in array)
        if (x % 2 == 0) yield return x;
}

Ja, das Schlüsselwort yield macht Magie – es implementiert das Interface für dich, aber dazu später mehr.

ICollection und IList – Collections mit Indexzugriff und Änderung

Wenn du nicht nur durchlaufen, sondern auch Elemente hinzufügen/entfernen oder per Index zugreifen willst, brauchst du spezialisiertere Interfaces:

  • ICollection<T> – fügt Methoden wie Add, Remove und die Property Count hinzu.
  • IList<T> – erweitert die möglichen Operationen, inklusive Indexzugriff (this[int index]).
public void DruckeErstesElement(IList<string> liste)
{
    if (liste.Count > 0)
        Console.WriteLine(liste[0]);
}

List<T> implementiert sowohl IEnumerable<T>, ICollection<T> als auch IList<T>. Das ist super praktisch – du kannst damit wie mit einer einfachen Liste arbeiten oder alle Methoden nutzen.

IDictionary<TKey, TValue> – Key-Value-Paare

Wenn du gerne mit Dictionaries arbeitest (und das passiert echt oft!), nutze das Interface IDictionary<TKey, TValue>. Es garantiert, dass du per Key einen Value bekommst und umgekehrt.

public void DruckeAllePaare(IDictionary<string, int> alter)
{
    foreach (var paar in alter)
        Console.WriteLine($"{paar.Key}: {paar.Value}");
}

Hier kann alter alles Mögliche sein, egal ob Dictionary<string, int> oder SortedDictionary<string, int> – Hauptsache, sie implementieren dieses Interface!

3. Das Interface IDisposable: Ressourcen richtig freigeben

Alles rund um I/O, Dateioperationen, Netzwerkverbindungen, Datenbanken in .NET läuft über das Interface IDisposable. Dieses Interface definiert einen lebenswichtigen Vertrag: Wenn ein Objekt unmanaged Ressourcen hat, muss es nach der Benutzung "aufgeräumt" werden. Ja, das ist der Typ, der den using-Syntax möglich macht:

using (StreamReader reader = new StreamReader("datei.txt"))
{
    // Wir arbeiten mit der Datei, und nach using wird sie garantiert geschlossen!
    string zeile = reader.ReadLine();
}

Wie sieht das Interface aus? Super simpel:

public interface IDisposable
{
    void Dispose();
}

Aber jede "ernsthafte" Bibliothek implementiert es! Mehr zu Best Practices findest du in der offiziellen Doku.

4. Interfaces für Vergleiche und Einzigartigkeit

IComparable<T> und IComparer<T>

Wenn du willst, dass deine Collection von Objekten sortiert werden kann, müssen die Objekte sich untereinander vergleichen können. Dafür gibt's das Interface IComparable<T>:

public class Student : IComparable<Student>
{
    public string Name { get; set; }
    public int Punkte { get; set; }

    public int CompareTo(Student? other)
    {
        // Sortierung nach absteigender Punktzahl
        if (other == null) return 1;
        return other.Punkte.CompareTo(this.Punkte);
    }
}

// Jetzt kann man Studenten sortieren:
var studenten = new List<Student> { ... };
studenten.Sort();

Mehr in der Microsoft-Doku.

IComparer<T> erlaubt es, den Vergleich außerhalb der Klasse zu definieren – zum Beispiel willst du Studenten mal nach Name, mal nach Punkten sortieren:

public class NameComparer : IComparer<Student>
{
    public int Compare(Student? x, Student? y)
    {
        return string.Compare(x?.Name, y?.Name);
    }
}

IEquatable<T> – Vergleich auf Gleichheit

Willst du, dass dein Objekt in einem HashSet<T> (also "einzigartig" in Collections) funktioniert? Implementiere IEquatable<T>:

public class Person : IEquatable<Person>
{
    public string Name { get; set; }
    public bool Equals(Person? other)
    {
        if (other == null) return false;
        return this.Name == other.Name;
    }
}

5. Interfaces für Events und Benachrichtigungen

Wolltest du schon mal, dass dein Programm "mitbekommt", wenn sich Properties eines Objekts ändern? Zum Beispiel soll das UI sich automatisch aktualisieren, wenn der User den Nachnamen ändert? Genau dafür gibt's das Interface INotifyPropertyChanged. Es ist super beliebt in GUI-Anwendungen (z.B. WPF oder Xamarin).

Die Signatur ist simpel:

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler? PropertyChanged;
}

Beispiel-Implementierung:

public class User : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    private string name;
    public string Name
    {
        get => name;
        set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
            }
        }
    }
}

Jetzt werden alle UI-Bindings zu diesem Objekt automatisch aktualisiert, wenn sich Name ändert. Falls du mehr wissen willst – lies die offizielle Doku.

6. Weitere nützliche Interfaces

Interface IFormattable: Flexibles Formatieren

Wenn du willst, dass dein Objekt hübsch und unterschiedlich als String ausgegeben werden kann (z.B. mit verschiedener Nachkommastellen-Anzahl), implementiere das Interface IFormattable:

public class Temperatur : IFormattable
{
    public double Celsius { get; }
    public Temperatur(double celsius) => Celsius = celsius;

    public string ToString(string? format, IFormatProvider? formatProvider)
    {
        // Nicht kompliziert machen, einfach 'C' oder 'F' je nach Format anzeigen
        if (format == "F")
            return $"{Celsius * 9 / 5 + 32} F";
        return $"{Celsius} C";
    }
}

Jetzt kannst du temp.ToString("F", null) oder temp.ToString("C", null) aufrufen. Deshalb unterstützen DateTime, double, decimal und andere eingebaute Typen flexible Formatierung.

Interfaces für Asynchronität

Mit dem Aufkommen von Asynchronität in C# brauchte man neue Interfaces für die Arbeit in der Async-Welt. Wenn dein Objekt Ressourcen asynchron freigibt – implementiere IAsyncDisposable:

public interface IAsyncDisposable
{
    ValueTask DisposeAsync();
}

Und jetzt schreibst du statt using einfach await using!

Mit asynchronen Enumerationen (await foreach) arbeitet das Interface IAsyncEnumerable<T>, das es erlaubt, Elemente zu durchlaufen, ohne den Main-Thread zu blockieren. Das nutzt man z.B. beim Lesen großer Dateien in Teilen oder beim Arbeiten mit Datenströmen aus dem Internet. Mehr dazu in Level 58 :P

Interfaces für Serialisierung: ISerializable

Wenn du Objekte in einen Bytestrom für Speicherung/Übertragung (z.B. übers Netzwerk) verwandeln willst, bietet .NET das Interface ISerializable. Es wird heute nicht mehr so oft genutzt, weil es bequemere Mechanismen gibt (z.B. über Attribute und fertige Serializer), aber eine Erwähnung ist es wert. Hier die Signatur:

public interface ISerializable
{
    void GetObjectData(SerializationInfo info, StreamingContext context);
}

7. Interfaces in der Praxis: Eine echte Anwendung

Angenommen, du bastelst eine Konsolenanwendung zur Verwaltung von Büchern in einer Bibliothek (hey, irgendwer muss das machen!). Du willst Listen von Büchern ausgeben, sie sortieren, filtern, Daten laden und speichern. Dank deines Interface-Wissens in .NET kannst du:

  • Verschiedene Collection-Typen zurückgeben über IEnumerable<Book>, damit der User sich nicht fragen muss, ob es ein Array oder eine Liste ist.
  • Sortierung nach verschiedenen Kriterien hinzufügen: Implementiere IComparable<Book> für Sortierung nach Titel und einen eigenen IComparer<Book> für den Autor.
  • Effizientes Freigeben von Ressourcen beim Arbeiten mit Dateien über IDisposable.
  • Bücher nach Genre oder Autor filtern mit LINQ, das mit allem funktioniert, was IEnumerable<T> implementiert.
  • Die Anwendung skalieren – z.B. das Dateispeicher durch eine Datenbank ersetzen, wenn deine Klassen über Interfaces (IBookStorage) und nicht über konkrete Implementierungen arbeiten.

8. Typische Fehler und Besonderheiten

Einer der häufigsten Anfängerfehler ist, konkrete Implementierungen ("List") statt Interfaces ("IEnumerable", "IList") bei Variablen- und Methodendeklarationen zu verwenden. MERKE: Wenn du "auf Interfaces programmierst", bleibt dein Code flexibel und leicht erweiterbar.

Manchmal musst du aufpassen, welches Interface-Level du verwendest – wenn du z.B. eine Methode schreibst, die nur Durchlaufen braucht, nimm IEnumerable<T> statt IList<T>, um keine unnötigen Einschränkungen zu machen.

Noch ein Kniff: Wenn eine Klasse viele Interfaces hat und es Überschneidungen bei Methoden oder Properties gibt, musst du explizite Interface-Implementierung (explicit implementation) nutzen, sonst versteht der Compiler dich nicht.

2
Aufgabe
C# SELF, Level 24, Lektion 3
Gesperrt
Überprüfung des IEnumerable-Interfaces
Überprüfung des IEnumerable-Interfaces
2
Aufgabe
C# SELF, Level 24, Lektion 3
Gesperrt
Sortieren mit dem Interface IComparable
Sortieren mit dem Interface IComparable
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION