9.9 Generische Collections
Ein ganz wesentlicher Nachteil der Auflistungen des Namespaces System.Collections ist, dass sie immer den Typ Object verwalten. Damit können Sie in einem Auflistungsobjekt alles speichern, von einem Integer über einen Stream bis hin zur Datenbanktabelle. Wissen Sie nicht, welche Typen von der Auflistung verwaltet werden, haben Sie praktisch keine Chance, die Elemente auszuwerten. Bedenken Sie, dass Sie jedes Element der Auflistung zuerst in den richtigen Typ konvertieren müssen, um dessen spezifische Eigenschaften nutzen zu können.
Generische Auflistungsklassen haben diesen Nachteil nicht. Sie sind für einen bestimmten Typ geprägt. Der Code wird dadurch einfacher und besser lesbar, und eine Konvertierung ist nicht notwendig, um auf die spezifischen Member der verwalteten Elemente zuzugreifen.
Im Wesentlichen habe ich Ihnen in Kapitel 8 zwei nichtgenerische Auflistungsklassen vorgestellt: die indexbasierte ArrayList und das Dictionary Hashtable. Beide haben einen generischen Gegenspieler in List<T> und Dictionary<TKey, TValue>. In diesem Abschnitt werde ich Ihnen List<T> vorstellen. Anhand des hier Gesagten erübrigt sich eine weitere Vertiefung der Klasse Dictionary<TKey, TValue>.
9.9.1 Die Interfaces der generischen Auflistungsklassen
Sie wissen, dass alle nichtgenerischen Auflistungsklassen die beiden Schnittstellen ICollection und IEnumerable implementieren. Zudem implementieren die indexbasierten Auflistungen die Schnittstelle IList, alle Schlüssel-Wert-Paar-Auflistungen hingegen die Schnittstelle IDictionary. Haben wir es mit generischen Auflistungsklassen zu tun, sind auch diese Schnittstellen generisch. Somit handelt es sich um
- IEnumerable<T>
- ICollection<T>
- IDictionary<TKey, TValue>
- IList<T>
Eine genauere Beschreibung dieser Interfaces ist nicht notwendig, da sie sich in ihrem elementaren Verhalten nicht von den nichtgenerischen unterscheiden.
9.9.2 Die generische Auflistungsklasse »List<T>«
Im Großen und Ganzen ist kein wesentlicher Unterschied zwischen den Methoden und Eigenschaften einer ArrayList und List<T> festzustellen. Sie fügen mit Add oder AddRange Objekte hinzu, Sie löschen Auflistungselemente mit Remove oder RemoveAt und besorgen sich den Index eines Elements mit IndexOf. Natürlich sind alle Methoden generisch geprägt, also auf einen bestimmten Typ hin spezialisiert, den Sie bei der Instanziierung der Klasse angegeben haben. Konzentrieren wir uns daher sofort auf ein Beispielprogramm.
Sortieren einer indexbasierten Collection
In Kapitel 8 habe ich Ihnen im Beispielprogramm IComparerSample den Einsatz von Vergleichsklassen vorgestellt, um die Elemente einer ArrayList nach eigenen Kriterien zu sortieren. Ausgangspunkt war die Klasse Person mit den beiden Feldern Name und City. In der Anwendung wurden mehrere Person-Objekte einem ArrayList-Objekt hinzugefügt und konnten entweder nach City oder Name sortiert an der Konsole ausgegeben werden. Vielleicht erinnern Sie sich noch, dass eine verhältnismäßig komplexe Konvertierung notwendig war, um den Rückgabewert des Vergleichs zu bilden. In der Vergleichsklasse NameComparer sah die Anweisung wie folgt aus:
return ((Person)x).Name.CompareTo(((Person)y).Name);
Mit der generischen Auflistungsklasse IList<T> geht alles viel einfacher:
List<Person> liste = new List<Person>();
Damit ist die Auflistung streng typisiert: Sie verwaltet nur Objekte vom Typ Person und wird andere Typen strikt abweisen.
Unser Ziel sei es auch im folgenden Beispiel, entweder nach Name oder City sortieren zu können. So wie die Klasse ArrayList bietet auch List<T> eine Methode Sort an, die das gewährleistet. Schauen wir uns deren Definition an:
public void Sort(IComparer<T> comparer)
Im Gegensatz zur Methode Sort der ArrayList wird nun ein Objekt erwartet, das eine generische Schnittstelle implementiert. Sehen wir uns zuerst die Definition der Schnittstellenmethode an:
public interface IComparer<T> {
int Compare(T x, T y);
}
Compare vergleicht zwei Objekte miteinander, deren Typ durch den Typparameter T beschrieben wird. Bezogen auf unser Beispiel bedeutet das, den Typparameter T durch Person zu ersetzen. Damit werden die Referenzen, die der Methode Compare als Argumente übergeben werden, vor dem Eintritt in den Code der Methode gefiltert.
Diese Erkenntnis wollen wir nun auch umsetzen. Dazu sind die beiden Klassen NameComparer und CityComparer im Vergleich zur ersten Version des Beispielprogramms ein wenig zu ändern – oder besser gesagt, zu vereinfachen. Anhand der Klasse CityComparer sei dies gezeigt.
// Vergleichsklasse – Kriterium City
class CityComparer : IComparer<Person> {
public int Compare(Person x, Person y) {
if (x == null && y == null) return 0;
if (x == null) return -1;
if (y == null) return 1;
return x.City.CompareTo(y.City);
}
}
Listing 9.16 Vergleichsklasse des Beispiels »GenericListSample«
Auch wenn der Umgang mit generischen Klassen und Schnittstellen im ersten Moment vielleicht ein wenig gewöhnungsbedürftig erscheint, ist der Vergleich zweier Objekte deutlich einfacher geworden, weil keine Typkonvertierung mehr notwendig ist. Das ist darauf zurückzuführen, dass wir durch Einsatz der generischen Schnittstellenmethode garantieren können, ausschließlich Person-Objekte zu vergleichen. Zudem können wir uns auch die Typüberprüfung sparen, die in der Ursprungsversion unseres Beispiels noch notwendig war.
Zum Schluss folgt hier der gesamte Code des Beispiels:
// Beispiel: ..\Kapitel 9\GenericListSample
class Program {
static void Main(string[] args) {
List<Person> liste = new List<Person>();
// generische Liste füllen
Person pers1 = new Person { Name = "Meier", City = "Berlin"};
liste.Add(pers1);
Person pers2 = new Person { Name = "Arnold", City = "Köln"};
liste.Add(pers2);
Person pers3 = new Person { Name = "Fischer", City = "Aachen"};
liste.Add(pers3);
// nach City sortieren
liste.Sort(new CityComparer());
Console.WriteLine("Liste nach Wohnorten sortiert");
ShowSortedList(liste);
// nach Namen sortieren
liste.Sort(new NameComparer());
Console.WriteLine("\nListe nach Namen sortiert");
ShowSortedList(liste);
Console.ReadLine();
}
static void ShowSortedList(IList<Person> liste) {
foreach (Person temp in liste) {
Console.Write("Name = {0,-12}", temp.Name);
Console.WriteLine("Wohnort = {0}", temp.City);
}
Console.WriteLine();
}
}
class Person {
public string Name {get; set; }
public string City { get; set; }
}
Listing 9.17 Das Beispielprogramm »GenericListSample«
9.9.3 Vergleiche mit Hilfe des Delegaten »Comparison<T>«
Im Vergleich zur Klasse ArrayList hat die Klasse List<T> noch eine weitere interessante Überladung der Methode Sort, die es uns gestattet, den Code noch kürzer und intuitiver zu schreiben. Sehen wir uns die Definition der Methode an.
public void Sort(Comparison<T> comparison)
Die Überladung erwartet die Übergabe eines Comparison<T>-Objekts. Dabei handelt es sich um einen Delegaten, der auf die Methode zeigt, die den Vergleich zweier T-Objekte durchführt und das Resultat des Vergleichs an den Aufrufer liefert. Hier auch noch die Definition des Delegaten:
public delegate int Comparison<T>(T x, T y);
Bezogen auf das Beispielprogramm GenericListSample können wir auf die beiden Vergleichsklassen verzichten und stattdessen zwei Methoden bereitstellen, die dasselbe leisten. Das folgende Beispielprogramm zeigt, wie Sie diese Überladung von Sort einsetzen können.
// Beispiel: ..\Kapitel 9\GenericListWithComparison
class Program {
static void Main(string[] args) {
List<Person> arrList = new List<Person>();
...
// nach City sortieren
arrList.Sort(CompareByCity);
Console.WriteLine("Liste nach Wohnorten sortiert");
ShowSortedList(arrList);
// nach Namen sortieren
arrList.Sort(CompareByName);
Console.WriteLine("Liste nach Namen sortiert");
ShowSortedList(arrList);
Console.ReadLine();
}
public static int CompareByName(Person x, Person y) {
// Prüfen auf null-Übergabe
if (x == null && y == null) return 0;
if (x == null) return -1;
if (y == null) return 1;
// Vergleich
return x.Name.CompareTo(y.Name);
}
public static int CompareByCity(Person x, Person y) {
// Prüfen auf null-Übergabe
if (x == null && y == null) return 0;
if (x == null) return -1;
if (y == null) return 1;
// Vergleich
return x.City.CompareTo(y.City);
}
static void ShowSortedList(IList<Person> liste) { ... }
}
Listing 9.18 Das Beispielprogramm »GenericListWithComparison«
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.