CodeGym /Kurse /C# SELF /Die Rolle von Interfaces in C#-Kollektionen

Die Rolle von Interfaces in C#-Kollektionen

C# SELF
Level 28 , Lektion 0
Verfügbar

1. Warum brauchen Kollektionen Interfaces?

Mal ehrlich: Solange wir Code mit einem konkreten Typ geschrieben haben (List<int>, Dictionary<string, string>), war alles easy und klar. Aber stell dir vor, du willst plötzlich List<T> durch HashSet<T> ersetzen, weil du nur noch einzigartige Werte brauchst. Oder du willst eine Methode schreiben, die mit beiden Typen von Kollektionen umgehen kann. Was tun?

Du brauchst einen gemeinsamen Einstiegspunkt – sozusagen einen „Universalanschluss“. In C# sind das Interfaces. Sie definieren einen Satz von Regeln (Methoden, Properties), die eine Kollektion haben muss. Alles, was ein Interface implementiert, unterstützt die angegebenen Operationen.

Das ist wie ein Ladegerät fürs Handy: Wenn du einen Standard-USB-C-Anschluss hast, kannst du damit dein Handy, einen modernen Staubsauger und sogar eine elektrische Zahnbürste laden (Hauptsache, du verwechselst sie nicht im Dunkeln).

Praxisbeispiel

Angenommen, du willst eine Methode schreiben, die die Summe aller Zahlen in irgendeiner Zahlenkollektion berechnet. So würde das ohne Interfaces aussehen:


// Diese Methode funktioniert nur mit Arrays:
static int SumArray(int[] array)
{
    int sum = 0;
    for (int i = 0; i < array.Length; i++)
    {
        sum += array[i];
    }
    return sum;
} 

// Und diese nur mit List<int>:
static int SumList(List<int> list)
{
    int sum = 0;
    for (int i = 0; i < list.Count; i++)
        sum += list[i];
    return sum;
}

Unpraktisch! Der Code ist doch fast gleich. Mit dem Interface IEnumerable<int> geht das universell:


static int SumAll(IEnumerable<int> collection)
{
    int sum = 0;
    foreach (var number in collection)
        sum += number;
    return sum;
}

Und jetzt nimmt diese Methode jeden Kollektionstyp, der IEnumerable<int> implementiert: Array, Liste, Set und so weiter.

2. Die wichtigsten Kollektionen-Interfaces

In .NET gibt es für Kollektionen ein paar zentrale Interfaces. Lass uns die kurz anschauen – in späteren Vorlesungen gehen wir auf die Details der Implementierung ein.

Interface Wofür gedacht Beispiel-Kollektionen, die das Interface implementieren
IEnumerable<T>
Elemente durchlaufen (foreach) Alle:
List<T>
,
HashSet<T>
, Arrays
ICollection<T>
Veränderbare Sammlung (Add, Remove, Count)
List<T>
,
HashSet<T>
,
Dictionary<K,V>
IList<T>
Zugriff per Index, Änderung
List<T>
, Arrays
IDictionary<K,V>
Arbeiten mit "Key-Value"-Paaren
Dictionary<K,V>
,
SortedDictionary<K,V>
ISet<T>
Menge (nur einzigartige Elemente)
HashSet<T>
,
SortedSet<T>

P.S. Zur Vereinfachung lassen wir erstmal die „mehrstufige“ Vererbung zwischen diesen Interfaces weg. Wer von wem erbt – dazu später mehr.

Visualisierung: Interface-Baum der Kollektionen


           IEnumerable<T>
                 ^
                 |
          ICollection<T>
              ^       ^
              |       |
         IList<T>  ISet<T>
              |       |
         List<T>   HashSet<T>
Interface-Hierarchie für Listen- und Mengen-Kollektionen

Für Dictionaries:


           IEnumerable<KeyValuePair<K,V>>
                      ^
                      |
               ICollection<KeyValuePair<K,V>>
                      ^
                      |
               IDictionary<K,V>
                      |
                Dictionary<K,V>
Interface-Hierarchie für Dictionaries

3. Wozu Interfaces im echten Leben?

Flexibilität

Wenn deine Methode oder Klasse nicht mit einem konkreten Typ (List<int>), sondern mit einem Interface (IEnumerable<int>) arbeitet, kannst du ihr jede Kollektion übergeben, die dieses Interface implementiert. Das macht dein Programm flexibel und erweiterbar. (Statt „Nur iPhone“ – jetzt geht jedes Handy mit Bluetooth.)


static void PrintAll(IEnumerable<string> collection)
{
    foreach (var item in collection)
        Console.WriteLine(item);
}

Jetzt kannst du Elemente jeder Liste, jedes Arrays, Sets oder sogar das Ergebnis einer LINQ-Abfrage ausgeben!

Vereinheitlichung

Wenn du deinen eigenen Kollektionstyp baust (zum Beispiel eine ganz spezielle Kollektion für irgendeine Aufgabe), implementiere die passenden Interfaces – und deine Kollektion funktioniert mit fremdem Code, der von deiner Kollektion gar nichts weiß. Deshalb liest man in .NET oft: „Programmiere auf Interface-Ebene, nicht auf Ebene der konkreten Implementierung“.

Kompatibilität mit Standard-Tools

Dank der Unterstützung von Standard-Interfaces können deine Kollektionen mit LINQ, Sortierung, Serialisierung und anderen .NET-Features arbeiten.

4. List<T> – ist Liste, Kollektion und Enumeration zugleich

Ein paar Worte zur Enumeration

IEnumerable<T> ist das wichtigste und grundlegendste Interface für Kollektionen in .NET. Es definiert genau eine Methode – GetEnumerator(), die ein Enumerator-Objekt zum Durchlaufen der Kollektion zurückgibt. Genau deshalb kannst du schreiben:


foreach (var name in names)
{
    Console.WriteLine(name);
}

Wobei names eine Liste, ein Set oder sogar ein Array sein kann. foreach ist syntaktischer Zucker, der unter der Haube die Methode GetEnumerator() nutzt.

Das Interface IEnumerable (ohne <T>) ist älter und nicht generisch. Heute solltest du fast immer die generische Version nehmen.

Syntax der List-Klassendeklaration

Schauen wir uns mal die Deklaration der Klasse List<T> an, wenn wir ein bisschen hinter die Kulissen blicken:


public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, ...

Das heißt, ein List<T>-Objekt kannst du verwenden als:

  • Liste mit beliebigem Indexzugriff (IList<T>)
  • Kollektion, in die du Elemente hinzufügen/entfernen kannst (ICollection<T>)
  • Einfach eine Menge von Elementen, die du durchlaufen kannst (IEnumerable<T>)

Beispiel:


List<string> names = new List<string> { "Anna", "Boris", "Vasya" };

// Als IEnumerable - durchlaufen mit foreach
IEnumerable<string> asEnumerable = names;
foreach (var n in asEnumerable) Console.WriteLine(n);

// Als ICollection - Count abfragen:
ICollection<string> asCollection = names;
int count = asCollection.Count;

// Als IList - Zugriff per Index:
IList<string> asList = names;
string firstItem = asList[0];

5. Beispielanwendung

Angenommen, wir haben eine einfache Anwendung zur Verwaltung von Benutzern und ihren Rollen (damit haben wir in den letzten Vorlesungen angefangen):


// Benutzerklasse
public class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Wir können Benutzer in jeder Kollektion speichern, die IEnumerable<User> implementiert:


List<User> listUsers = new List<User> { new User { Name = "Anna", Age = 20 } };
HashSet<User> setUsers = new HashSet<User> { new User { Name = "Boris", Age = 25 } };

// Methode, die alle Benutzernamen ausgibt
static void PrintUserNames(IEnumerable<User> users)
{
    foreach (var user in users)
        Console.WriteLine(user.Name);
}

// Mit verschiedenen Kollektionstypen verwenden:
PrintUserNames(listUsers);
PrintUserNames(setUsers);

Jetzt ist die Anwendung viel flexibler – ganz ohne Magie oder spezielle Überladungen!

6. Typische Fehler und Stolperfallen

Einer der häufigsten Fehler ist, zu konkrete Typen zu verwenden, wo ein Interface reichen würde:


// Schlecht (zu fest an die Implementierung gebunden)
void DoSomethingWithList(List<int> numbers) { ... }

// Besser (Methode funktioniert mit jeder Kollektion)
void DoSomethingWithNumbers(IEnumerable<int> numbers) { ... }

Noch eine Falle: Vergessen, dass ein Interface nur die Operationen vorgibt, die explizit darin stehen. Zum Beispiel kannst du bei IEnumerable<T> nicht die Anzahl der Elemente (Count) abfragen – dafür brauchst du ICollection<T> oder du zählst sie selbst.

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