6.2 Sammlungsklassen
Der folgende Ausschnitt aus der Vererbungshierarchie zeigt einige spezifische Sammlungen und mit ihnen zusammenhängende Klassen. Um die Übersicht kurz zu halten sind optionale Namensteile in eckige Klammern gesetzt.
Object + 1-+Comparer(Of T) | +[Sorted]Dictionary(Of TKey,TValue) | +EqualityComparer(Of T) | +HashSet(Of T) | +LinkedList[Node](Of T) | +[Sorted]List(Of T) | +Queue(Of T) | +Stack(Of T) | +SynchronizedCollection(Of T) | | + 1--SynchronizedKeyedCollection(Of String,ClientOperation|...) | +SynchronizedCollection(Of IExtension(Of T As | | | {IExtensibleObject(Of T)})})) | | + 4--ExtensionCollection(Of T As {IExtensibleObject(Of T)})}) | +SynchronizedReadOnlyCollection(Of T) + 2-+Collection(Of T) | + 2-+KeyedCollection(Of TKey, TItem) | | | + 1--KeyedByTypeCollection(Of TItem) | | +ObservableCollection(Of T) | + 3--BindingList(Of T) +ReadOnlyCollection(Of T) + 2--ReadOnlyObservableCollection(Of T) 1: System.Collections.Generic 2: System.Collections.ObjectModel 3: System.ComponentModel 4: System.ServiceModel
Bevor wir einige Klassen im Detail anschauen, zeigt Tabelle 6.5 erst einmal eine Übersicht über einige Sammlungen. Auch hier sind optionale Teile in eckige Klammern gesetzt.
Klasse | Beschreibung |
[Synchronized][ReadOnly] Collection(Of T) |
Werteliste, die threadsicher (Synchronized) und/oder schreibgeschützt (ReadOnly) sein kann |
[ReadOnly] ObservableCollection(Of T) |
[Schreibgeschützte] Werteliste, die automatisch über Änderungen informiert, und mit Methoden zum Bewegen von Elementen und Kopieren in ein Array |
[Sorted]List(Of T) |
[Sortierte] Werteliste mit Indexzugriff und Methoden zum Suchen und Kopieren in ein Array |
HashSet(Of T) |
Wertemenge ohne Doubletten (Hashcode durch Comparer) mit Mengenoperationen (Vereinigung, Komplement, Schnitt, Teilmenge) und Kopie in ein Array |
[Sorted]Dictionary (Of Key,Value) |
Hashbasierte [sortierte] Schlüssel (durch Comparer berechnet) werden auf Werte abgebildet, Doubletten und Nothing-Schlüssel sind verboten. |
KeyedCollection (Of TKey, TItem) |
Liste von TItem Werten, die einen TKey Schlüssel enthalten (Kombination von IList und IDictionary). |
LinkedList(Of T) LinkedListNode(Of T) |
Doppelt verkettete Liste aus Knoten (Node) mit Methoden zum Löschen/Einfügen an den Enden sowie relativ zu einem Eintrag, Wertsuche von den Enden, Kopie in ein Array |
Queue(Of T) |
FiFo-Schlange mit Methoden zum Einstellen, Auslesen, Abholen und Kopieren in ein Array |
Stack(Of T) |
LiFo-Stapel mit Methoden zum Einstellen, Auslesen, Abholen und Kopieren in ein Array |
Comparer(Of T) |
Sortierer, für zum Beispiel SortedList(Of Key,Val), SortedDictionary(Of Key,Val), List(Of T).Sort und List(Of T).BinarySearch. Implementiert IComparer(Of T). Eigenschaft Default liefert Standardsortierer. |
EqualityComparer(Of T) |
Vergleicher, zum Beispiel für Schlüssel, Eigenschaft Default liefert Standardvergleicher und verwendet IEquatable(Of T) bzw. Object.Equals und Object.GetHashCode. Implementiert IEqualityComparer(Of T) . |
6.2.1 Collection
Die einfachste Implementierung einer Sammlung ist Collection(Of T). Über die implementierten Schnittstellen IList(Of T), ICollection(Of T), IEnumerable(Of T), IList, ICollection und IEnumerable hinaus stellt sie keine zusätzliche Funktionalität zur Verfügung. Die dem Konstruktor übergebene optionale Sammlung wird direkt verwendet, es wird also mit dem Original gearbeitet. Die Schnittstellenfunktionalität ist in Abschnitt 6.1, »Sammlungsschnittstellen«, beschrieben.
Etwas mehr Komfort bietet ObservableCollection(Of T) durch die Implementierung der Schnittstellen INotifyCollectionChanged und INotifyPropertyChanged mit der Möglichkeit, Änderungen an der Sammlung zu verfolgen. Die Klasse ist in Tabelle 6.6 aufgelistet und ist in der Bibliothek WindowsBase.dll definiert, die gegebenenfalls dem Projekt hinzugefügt werden muss.
Klasse | Beschreibung |
New([list As List(Of T)]) |
Öffentlicher Konstruktor |
CollectionChanged As NotifyCollectionChangedEventHandler |
Auslösung bei Änderung der Zusammensetzung der Sammlung (Registrierung automatisch) |
PropertyChanged As PropertyChangedEventHandler |
Auslösung bei Änderung eines Elements (nach manueller Registrierung) |
Move(old As Integer, new As Integer) |
Element verschieben |
Wie das folgende Beispiel zeigt, kann die Überwachung auch sehr gut außerhalb von Benutzeroberflächen genutzt werden.
'...\Sammlungen\Klassen\ObservableCollection.vb |
Option Strict On Imports System.Collections.ObjectModel Imports System.Collections.Specialized Namespace Sammlungen Module Observable Public Sub CollectionChanged(ByVal sender As Object, _ ByVal ev As NotifyCollectionChangedEventArgs) If ev.NewItems IsNot Nothing Then For Each el As String In ev.NewItems Console.Write("Hallo {0} ", el) : Next End If If ev.OldItems IsNot Nothing Then For Each el As String In ev.OldItems Console.Write("Tschö {0} ", el) : Next End If End Sub Sub Test() Dim feier As ObservableCollection(Of String) = _ New ObservableCollection(Of String)() AddHandler feier.CollectionChanged, AddressOf CollectionChanged feier.Add("Ganymed") : feier.Add("Europa") : feier.Add("Io") feier.Add("Kallisto") : feier.Remove("Io") : feier.Remove("Europa") Console.ReadLine() End Sub End Module End Namespace
Die Reaktion auf Änderungen der Collection erfolgt automatisch.
Hallo Ganymed Hallo Europa Hallo Io Hallo Kallisto Tschö Io Tschö Europa
6.2.2 List
Deutlich umfangreicher und komfortabler ist List(Of T) (und damit auch ArrayList). Es implementiert dieselben Schnittstellen wie Collection. Um Tabelle 6.7 möglichst kurz zu halten, habe ich kleingeschriebene Parameternamen und großgeschriebene Typen gemischt.
Klasse | Beschreibung |
New([capacity | IEnumerable(Of T)]) |
Öffentlicher Konstruktor |
TrimExcess() |
Kapazität der Ist-Größe anpassen |
AddRange(IEnumerable(Of T)) InsertRange(index, IEnumerable(Of T)) |
Andere Liste anhängen bzw. einfügen |
RemoveRange(index, count) RemoveAll(Predicate(Of T)) As Integer |
Bereiche nach Index oder Kriterium löschen |
BinarySearch([from, count, ]item [, IComparer(Of T)]) As Integer Find[Last]Index([from, [count,]] Predicate(Of T)) As Integer [Last]IndexOf(item [,index [,count]]) As Integer |
Position eines Elements mittels Vergleich durch implementierende Klasse oder Delegate (IndexOf nutzt EqualityComparer(Of T).Default). |
Find[Last](Predicate(Of T)) As T |
Element, das das Kriterium erfüllt |
FindAll(Predicate(Of T)) As List(Of T) |
Elemente, die das Kriterium erfüllen |
AsReadOnly() As ReadOnlyCollection(Of T) |
Schreibgeschützte Referenz (Original ist änderbar) |
Reverse([index, count]) |
Elementreihenfolge umdrehen |
Sort([[index, count, ] IComparer(Of T)]) Sort(comparison(Of T)) |
Umsortierung mit implementierender Klasse oder Delegate |
CopyTo([sourceIndex,] array, [arrayIndex [, count]]) ToArray() As T() |
Flache Kopie in ein eindimensionales Feld mit Array.Copy |
GetRange(index, count) As List(Of T) |
Flache Kopie eines Bereichs der Liste |
ConvertAll(Converter(Of T,TOutput)) As List(Of TOutput) |
Neue Liste, elementweise mit converter konvertiert |
ForEach(Action(Of T)) |
Aktion elementweise anwenden |
Exists(Predicate(Of T)) As Boolean |
Ein Element genügt dem Kriterium. |
TrueForAll(Predicate(Of T)) As Boolean |
Alle Element genügen dem Kriterium. |
Insbesondere hat diese Auflistung die Möglichkeiten, Elemente zu suchen sowie die gesamte Auflistung zu sortieren. Fangen wir mit dem Sortieren an. Jede Sortiermethode muss zu sortierende Elemente vergleichen. Bei der großen Vielfalt an Typen gibt es keine allgemeingültigen Kriterien, nach denen sortiert werden könnte. Daher setzt die Methode Sort dazu die in der Schnittstelle IComparable definierte Methode CompareTo voraus und überlässt es implementierenden Klassen, zu definieren, was kleiner und größer ist. Sie sind sowieso die einzigen, die das überhaupt entscheiden können.
Public Interface IComparable(Of T)
Function CompareTo(ByVal other As T) As Integer
End Interface |
Die Methode liefert einen negativen Wert, wenn das aktuelle Objekt »kleiner« als das übergebene ist. Analog ist der Wert positiv, wenn es »größer« ist. Sind beide »gleich«, ist der Rückgabewert Null. Die absoluten Zahlenwerte spielen keine Rolle. Zum Beispiel sind die Rückgabewerte -27 und -1 gleichwertig. Der Typ der implementierenden Klasse muss nicht mit dem des Typparameters übereinstimmen.
Im folgenden Beispiel werden Bücher aufgrund ihrer Seitenzahl sortiert. Die Klasse Buch implementiert die Vergleichsschnittstelle. Anstatt selbst den Vergleich durchzuführen, reicht CompareTo den Vergleich an den Typ Integer durch, der die Schnittstelle auch implementiert. Lediglich der Fall einer Nullreferenz ist gesondert zu behandeln.
'...\Sammlungen\Klassen\Sortieren.vb |
Option Strict On Namespace Sammlungen Module Sortieren Class Buch : Implements IComparable(Of Buch) Private titel As String Private seitenzahl As Integer Sub New(ByVal t As String, ByVal s As Integer) titel = t : seitenzahl = s End Sub Public Function CompareTo(other As Buch) As Integer Implements IComparable(Of Buch).CompareTo If other Is Nothing Then Return 100 Return seitenzahl.CompareTo(other.seitenzahl) End Function Public Overrides Function ToString() As String Return titel & " (" & seitenzahl & ")" End Function End Class Sub Test() Dim Bücher As New List(Of Buch) Bücher.Add(New Buch("Fähnrich Hornblower", 317)) Bücher.Add(New Buch("Der Kapitän", 245)) Bücher.Add(Nothing) Bücher.Add(New Buch("Der Kommodore", 351)) Bücher.Add(New Buch("Lord Hornblower", 301)) For Each b As Buch In Bücher Console.Write(If(b Is Nothing, "-", b.ToString()) & " ") : Next Console.WriteLine() Bücher.Sort() For Each b As Buch In Bücher Console.Write(If(b Is Nothing, "-", b.ToString()) & " ") : Next Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe zeigt die korrekte Sortierung nach der Seitenzahl.
Fähnrich Hornblower (317) Der Kapitän (245) – Der Kommodore (351) Lord Hornblower (301) - Der Kapitän (245) Lord Hornblower (301) Fähnrich Hornblower (317) Der Kommodore (351)
Eine andere Möglichkeit, zu vergleichen, besteht in der Übergabe eines Objekts, das den Vergleich durchführt. Das vergleichende Objekt muss die Schnittstelle IComparer implementieren, die IComparable sehr ähnlich ist.
Public Interface IComparer(Of T)
Function [Compare](ByVal x As T, ByVal y As T) As Integer
End Interface |
Der Rückgabewert ist genauso organisiert wie bei IComparable. Daher ist das folgende Beispiel dem letzten sehr ähnlich:
'...\Sammlungen\Klassen\Sortierung.vb |
Option Strict On Namespace Sammlungen Module Sortierung Class Buch Private titel As String Friend seitenzahl As Integer Sub New(t As String, s As Integer) titel = t : seitenzahl = s End Sub Public Overrides Function ToString() As String Return titel & " (" & seitenzahl & ")" End Function End Class Class Vergleich : Implements IComparer(Of Buch) Public Function Compare(x As Buch, y As Buch) As Integer _ Implements System.Collections.Generic.IComparer(Of Buch).Compare If x Is Nothing AndAlso y Is Nothing Then Return 0 If x Is Nothing Then Return –1 If y Is Nothing Then Return 1 Return x.seitenzahl.CompareTo(y.seitenzahl) End Function End Class Sub Test() Dim Bücher As New List(Of Buch) Bücher.Add(New Buch("Fähnrich Hornblower", 317)) Bücher.Add(New Buch("Der Kapitän", 245)) Bücher.Add(Nothing) Bücher.Add(New Buch("Der Kommodore", 351)) Bücher.Add(New Buch("Lord Hornblower", 301)) For Each b As Buch In Bücher Console.Write(If(b Is Nothing, "-", b.ToString()) & " ") : Next Console.WriteLine() Bücher.Sort(New Vergleich()) For Each b As Buch In Bücher Console.Write(If(b Is Nothing, "-", b.ToString()) & " ") : Next Console.ReadLine() End Sub End Module End Namespace
Die Sortierung ist identisch zur letzten:
Fähnrich Hornblower (317) Der Kapitän (245) – Der Kommodore (351) Lord Hornblower (301) - Der Kapitän (245) Lord Hornblower (301) Fähnrich Hornblower (317) Der Kommodore (351)
Einige Suchfunktionen arbeiten mit einer Testfunktion. Sowie diese True zurückgibt, gilt der als Argument übergebe Wert als der gefundene. Die Testfunktion kann als Delegate oder als Funktionsobjekt gegeben werden, wie im folgenden Beispiel. Dort wird die erste Zahl gesucht, die größer als 70 ist.
'...\Sammlungen\Klassen\Finden.vb |
Option Strict On Namespace Sammlungen Module Finden Function Siebzig(ByVal z As Integer) As Boolean Return z > 70 End Function Sub Test() Dim rnd As New Random(1) Dim zahlen As New List(Of Integer)() For i As Integer = 0 To 10 zahlen.Add(rnd.Next(0, 100)) Next For Each z As Integer In zahlen : Console.Write(z & " ") : Next Console.WriteLine() Console.WriteLine("Erste größer als 70: {0}", _ zahlen.Find(Function(z As Integer) z > 70)) Console.WriteLine("Erste größer als 70: {0}", _ zahlen.Find(AddressOf Siebzig)) Console.ReadLine() End Sub End Module End Namespace
Die Zahl wird auf beide Arten korrekt gefunden:
24 11 46 77 65 43 35 94 10 64 2 Erste größer als 70: 77 Erste größer als 70: 77
6.2.3 Wörterbücher
Alle Wörterbuchklassen implementieren die Schnittstelle IDictionary, die bereits im Abschnitt 6.1, »Sammlungsschnittstellen«, vorgestellt wurde. Hier möchte ich noch auf ein Problem sortierter Auflistungen hinweisen, da Wörterbücher oft sortiert gespeichert werden. Der Sortiervorgang setzt einen Vergleich der Elemente der Auflistung voraus. Wenn Sie dem Konstruktor kein Objekt vom Typ IComparer(Of T) übergeben, wird ein Standardvergleich durchgeführt, der nicht immer passend ist.
Die einzelnen Einträge sind Paare von Schlüsseln und Werten. Wenn Sie ein Wörterbuch durchlaufen, ist der Typ des Elements weder der des Schlüssels noch der des Wertes, sondern des Paares. Im folgenden Beispiel werden Paare von Zeichenketten und Zahlen verarbeitet.
'...\Sammlungen\Klassen\Paar.vb |
Option Strict On Namespace Sammlungen Module Paar Sub Test() Dim tab As New Dictionary(Of String, Integer)() tab.Add("Perm", 299) : tab.Add("Kreide", 145) tab.Add("Karbon", 360) : tab.Add("Jura", 200) For Each p As KeyValuePair(Of String, Integer) In tab Console.Write("Beginn {0} vor {1} Mio Jahren ", p.Key, p.Value) Next Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe bestätigt die korrekte Arbeitsweise:
Beginn Perm vor 299 Mio Jahren Beginn Kreide vor 145 Mio Jahren Beginn Karbon vor 360 Mio Jahren Beginn Jura vor 200 Mio Jahren
6.2.4 Schlangen
Eine Gruppe von Auflistungen ist dadurch gekennzeichnet, dass nur auf die Elemente an einem der beiden Enden zugegriffen werden kann. Aufgrund der Art von Änderungen werden zwei Ausprägungen unterschieden:
- FiFo (first in first out): Das erste hinzugefügte Element wird auch als Erstes wieder entnommen, implementiert von der Klasse Queue(Of T) .
- LiFo (last in first out): Das letzte hinzugefügte Element wird als Erstes wieder entnommen, implementiert von der Klasse Stack(Of T) .
Im folgenden Beispiel werden dieselben Zahlen mit Push in einem Stack und mit Enqueue in einer Queue abgelegt und mit Pop bzw. Dequeue wieder ausgelesen.
'...\Sammlungen\Klassen\Schlangen.vb |
Option Strict On Namespace Sammlungen Module Schlangen Sub Test() Dim st As New Stack(Of Integer), qu As New Queue(Of Integer) For Each no As Integer In New Integer() {17, 45, 7, 82, 8} Console.Write(no & " ") : st.Push(no) : qu.Enqueue(no) Next Console.WriteLine() While st.Count > 0 Console.Write(st.Pop() & " ") End While Console.WriteLine() While qu.Count > 0 Console.Write(qu.Dequeue() & " ") End While Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe zeigt, dass Stack die Reihenfolge umkehrt und Queue sie beibehält.
17 45 7 82 8 8 82 7 45 17 17 45 7 82 8
6.2.5 Gleichheit
Die Schnittstellen IEquatable(Of T) und IComparable(Of T) werden leicht vergessen, wenn es darum geht, was unter Gleichheit von Objekten zu verstehen ist. Sie sind jedoch wichtig in Zusammenhang mit Sammlungen.
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.