1. Einführung
Mit IEnumerable<T> konntest du nur durch den bereits vorhandenen Inhalt einer Collection spazieren, aber das Interface ICollection<T> ist dein Ticket in die Welt der Collection-Verwaltung: Elemente hinzufügen, entfernen, Anzahl prüfen, in ein Array kopieren und sogar Änderungen überwachen (fast wie im echten Leben, wenn du versuchst, den Inhalt deines Einkaufskorbs zu kontrollieren).
ICollection<T> wird von allen veränderbaren .NET-Collections implementiert, wie List<T>, HashSet<T>, Dictionary<TKey, TValue>.ValueCollection und sogar weniger populären wie Queue<T>, Stack<T> (ja, sogar Queues und Stacks!). Das ist der grundlegende Vertrag für alle Collection-Klassen, die ihr Zeug verändern lassen.
Die Familie der Collection-Interfaces
graph TD
A[IEnumerable<T>]
B[ICollection<T>]
C[IList<T>]
D[IDictionary<TKey, TValue>]
E[Queue<T>, Stack<T>, List<T>, HashSet<T>...]
A --> B
B --> C
B --> D
B --> E
2. Was steckt im Interface ICollection<T>?
Im Gegensatz zu IEnumerable<T>, das nur fürs Durchlaufen (foreach) zuständig ist, ist ICollection<T> wie das Schweizer Taschenmesser unter den Interfaces: Für jede Aufgabe gibt's eine Methode!
Hier das Wichtigste:
public interface ICollection<T> : IEnumerable<T>
{
int Count { get; }
bool IsReadOnly { get; }
void Add(T item);
void Clear();
bool Contains(T item);
void CopyTo(T[] array, int arrayIndex);
bool Remove(T item);
}
Liste der Methoden und Eigenschaften:
- Count — Anzahl der Elemente in der Collection. Wie deine Lieblings-Zählfunktion.
- IsReadOnly — Kann man die Collection ändern? Wenn true, ist sie nur lesbar (z.B. bei manchen Wrappern).
- Add(T item) — Fügt ein Element zur Collection hinzu. Eines deiner wichtigsten Tools!
- Clear() — Entfernt alle Elemente. Komplett sauber machen.
- Contains(T item) — Schneller Check: Gibt's das Element in der Collection?
- CopyTo(T[] array, int arrayIndex) — Kopiert die Elemente in ein normales Array, ab einem bestimmten Index. Praktisch für alte APIs oder "legacy" Methoden.
- Remove(T item) — Entfernt ein Element (falls vorhanden).
3. Echte Aufgaben für Programmierer
Warum ist es wichtig zu verstehen, wie dieses Interface funktioniert? In der Praxis schreibst du oft generischen Code, der mit beliebigen Collections arbeitet. Zum Beispiel kann eine Funktion jede Liste, Queue oder Menge akzeptieren – egal, welcher Collection-Typ übergeben wurde. Solange die Collection ICollection<T> implementiert, kannst du Elemente hinzufügen, entfernen, Größe prüfen und sogar ins Array kopieren. Das ist die Basis für flexible Libraries und universelle Algorithmen.
Beispiel aus dem Leben: Das Interface ICollection<T> taucht oft in Methodenparametern und API-Properties auf, die eine veränderbare Collection zurückgeben. Zum Beispiel in beliebten ORMs (Entity Framework, Dapper) werden Properties vom Typ ICollection<T> genutzt, um Collections von verknüpften Objekten zu beschreiben.
Beispiele für die Nutzung
Lass uns eine universelle Methode schreiben, die jede Collection nimmt und ein Element hinzufügt. Hauptsache, sie implementiert ICollection<T>.
// Universelle Methode zum Hinzufügen eines Elements
void AppendItem<T>(ICollection<T> collection, T item)
{
collection.Add(item);
}
Jetzt kannst du die Methode mit List<T>, HashSet<T> oder sogar deiner eigenen Interface-Implementierung nutzen! So sieht das aus:
var numbers = new List<int> { 1, 2, 3 };
AppendItem(numbers, 42);
var uniqueNames = new HashSet<string> { "Alice", "Bob" };
AppendItem(uniqueNames, "Charlie");
Schon witzig – uns ist egal, ob es eine Liste oder eine Menge ist, Hauptsache es ist ICollection<T>. Nicht wundern, so funktionieren viele universelle Methoden in .NET!
4. Verbindung zu anderen Collections
ICollection<T> ist nicht nur ein theoretisch schöner Vertrag, sondern das echte Fundament für fast alle Collections, die du in .NET triffst.
Um zu sehen, wie universell das ist, probier mal diesen Code:
void ShowCollectionInfo<T>(ICollection<T> collection)
{
Console.WriteLine($"Anzahl der Elemente: {collection.Count}");
Console.WriteLine($"Kann man ändern: {!collection.IsReadOnly}");
Console.WriteLine("Elemente:");
foreach (var item in collection)
{
Console.WriteLine(item);
}
}
// Für List<T>
var list = new List<string> { "apfel", "orange" };
ShowCollectionInfo(list);
// Für HashSet<T>
var set = new HashSet<string> { "apfel", "orange" };
ShowCollectionInfo(set);
Das Ergebnis ist für jeden Collection-Typ, der das Interface implementiert, gleich korrekt. Probier's mit Queue, Stack oder sogar einem Array-Wrapper!
5. Nur-lesbare Collections
Das Interface ICollection<T> hat die Eigenschaft IsReadOnly, die angibt, ob die Collection verändert werden kann. Nicht verwechseln mit der gewohnten Eigenschaft bei Arrays! Wenn du eine Collection hast, bei der IsReadOnly == true, führt ein Versuch, ein Element hinzuzufügen oder zu entfernen, zu einer Exception.
Beispiel:
// Wir erstellen ein Array und wrappen es in eine ReadOnlyCollection
var array = new int[] { 1, 2, 3 };
ICollection<int> readOnly = Array.AsReadOnly(array);
// Wir versuchen, ein Element hinzuzufügen
try
{
readOnly.Add(4); // Wird NotSupportedException werfen
}
catch (NotSupportedException)
{
Console.WriteLine("Kann keine Elemente hinzufügen: Collection ist nur lesbar!");
}
Das passiert z.B., wenn du eine Collection aus einer externen Quelle bekommen hast und sie nicht ändern darfst – aber du kannst die Elemente lesen und die Anzahl abfragen.
6. Vergleichstabelle der Methoden populärer Collections im Kontext von ICollection<T>
| Collection | Add() | Remove() | Contains() | Clear() | CopyTo() | IsReadOnly |
|---|---|---|---|---|---|---|
|
Ja | Ja | Ja | Ja | Ja | Nein |
|
Ja* | Ja | Ja | Ja | Ja | Nein |
|
Nein/Nein | Nein/Nein | Nein/Nein | Ja/Ja | Ja/Ja | Nein |
|
Nein | Nein | Ja | Nein | Ja | Ja |
|
Nein | Nein | Ja | Nein | Ja | Ja/Nein* |
Bei HashSet<T> gibt die Methode Add true zurück, wenn das Element hinzugefügt wurde, sonst false.
7. CopyTo – wofür braucht man das?
Die Methode CopyTo erlaubt es dir, den Inhalt einer Collection schnell in ein normales Array zu schieben. Praktisch, wenn du z.B. Daten an eine alte API zurückgeben musst, die nur Arrays nimmt, oder eine Massenverarbeitung mit Array-Methoden machen willst.
var contactsArray = new Contact[book.Count];
((ICollection<Contact>)book).CopyTo(contactsArray, 0);
// Jetzt kannst du contactsArray auf die alte Art nutzen
8. Beispiel
In unserem kleinen Konsolenprojekt nehmen wir an, wir entwickeln ein Adressbuch. Wir haben eine Klasse Contact, und das Adressbuch kann auf jeder Collection basieren, solange sie Hinzufügen, Entfernen und Durchlaufen unterstützt.
public class Contact
{
public string Name { get; set; }
public string Phone { get; set; }
}
public class AddressBook
{
private readonly ICollection<Contact> _contacts;
public AddressBook(ICollection<Contact> contacts)
{
_contacts = contacts;
}
public void AddContact(Contact contact)
{
_contacts.Add(contact);
}
public bool RemoveContact(Contact contact)
{
return _contacts.Remove(contact);
}
public int Count => _contacts.Count;
public void PrintAll()
{
foreach (var contact in _contacts)
{
Console.WriteLine($"{contact.Name} — {contact.Phone}");
}
}
}
Jetzt kann unser Adressbuch mit List<Contact>, HashSet<Contact> oder sogar – später – mit einer eigenen Collection arbeiten, die wir auf Basis einer Datenbank oder Datei bauen! Beispiel für die Nutzung:
var book = new AddressBook(new List<Contact>());
book.AddContact(new Contact { Name = "Ivan", Phone = "+7-123-456" });
book.AddContact(new Contact { Name = "Maria", Phone = "+7-987-654" });
Console.WriteLine($"Gesamtanzahl Kontakte: {book.Count}");
book.PrintAll();
Wenn wir Duplikate vermeiden wollen, können wir dem AddressBook statt einer Liste einfach eine Menge übergeben:
var book = new AddressBook(new HashSet<Contact>());
(Voraussetzung: Contact muss Vergleich korrekt implementieren, aber das ist ein Thema für eine andere Vorlesung!)
9. Besonderheiten der Methoden und typische Fehler
Direkt ein Hinweis: Nicht alle Collections, die ICollection<T> implementieren, verhalten sich gleich! Zum Beispiel führt der Versuch, eine Collection zu ändern, wenn sie IsReadOnly == true hat, zu einer Exception. Und wenn du mit HashSet<T> arbeitest, gibt die Methode Add true nur zurück, wenn das Element wirklich neu ist (vorher nicht drin war). Außerdem kann die Performance der Methoden je nach Collection unterschiedlich sein – das Interface schreibt das aber nicht vor.
Noch ein häufiger Punkt: Wenn die Collection von mehreren Threads genutzt wird, kann das Ändern in einem Thread und das Durchlaufen in einem anderen zu Exceptions führen. Für Multithreading brauchst du spezielle Collections (ConcurrentBag<T>, ConcurrentQueue<T> usw. – dazu später mehr).
Noch eine Falle: Wenn du eine Collection mit CopyTo kopierst, sind danach Änderungen am Array und an der Collection unabhängig voneinander. Das Array ist ein eigener Speicherbereich.
GO TO FULL VERSION