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 |
|---|---|---|
|
Elemente durchlaufen (foreach) | Alle: , , Arrays |
|
Veränderbare Sammlung (Add, Remove, Count) | , , |
|
Zugriff per Index, Änderung | , Arrays |
|
Arbeiten mit "Key-Value"-Paaren | , |
|
Menge (nur einzigartige Elemente) | , |
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>
Für Dictionaries:
IEnumerable<KeyValuePair<K,V>>
^
|
ICollection<KeyValuePair<K,V>>
^
|
IDictionary<K,V>
|
Dictionary<K,V>
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.
GO TO FULL VERSION