Im vierten Teil von “LinkedList Dojo mit Visual T#” nehmen wir uns den im Teil 3 noch fehlenden Implementierungen an. Folgende Methoden werfen immer noch die NotImplementedException:
- Insert(int index, T item)
- RemoveAt(int index)
- CopyTo(T[] array, int arrayIndex)
- IsReadOnly
- Remove(T item)
- GetEnumerator() in den Varianten „Generics“ und „Non-Generics“
Wagen wir und gleich an den wahrscheinlich grössten Brocken, die zwei GetEnumerator()-Methoden. Die Implementierung der Methoden wird dabei nicht das schwierige und aufwändige sein, sonder der Enumerator, der zurückgegeben wird.
Erstellen wir also eine neue Klasse für den Enumerator und im Testprojekt die zugehörigen Tests.
Als erstes die zwei Tests, die den Enumerator verlangen:
GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)
Für den nicht generischen Enumerator muss unsere Liste auf das nicht generische Interface gecastet werden, bevor die Funktion aufgerufen werden kann.
Um die Test grün werden zu lassen benötigen wir nun die Implementation des Enumerators. Ich habe mich dabei entschieden, eine eigene Klasse auf der selben Ebene wie die Liste zu erstellen und keine verschachtelte Klasse.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | namespace RolandBaer.LinkedList { public class LinkedListEnumerator : IEnumerator { public T Current { get { throw new NotImplementedException(); } } public void Dispose() { throw new NotImplementedException(); } object System.Collections.IEnumerator.Current { get { throw new NotImplementedException(); } } public bool MoveNext() { throw new NotImplementedException(); } public void Reset() { throw new NotImplementedException(); } } } |
Jetzt noch die beiden GetEnumerator-Methoden implementieren:
1 2 3 4 5 6 7 8 9 | public IEnumerator GetEnumerator() { return new LinkedListEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } |
Da das generische Interface das nicht-generische beinhaltet kann in der nicht-generischen Methode auf die generische Methode zurückgegriffen werden. So habe ich schon eine Redundanz verhindert.
Durchnummerieren!
Viel sinnvolles können wir aber mit der Implemetation des Enumerators noch nicht anfangen. Schreiben wir deshalb ein paar Tests, die den Enumerator testen.
Als erstes testen wir, ob MoveNext false zurückliefert, wenn wir eine leere Liste haben. Wie wir die Liste übergeben, haben wir noch gar nicht überlegt, aber wenn keine Liste bekannt ist muss sicher false zurückgegeben werden.
GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)
Um den Test grün werden zu lassen müssen wir MoveNext anpassen.
1 2 3 4 | public bool MoveNext() { return false; } |
Wie gesagt, die Liste muss dem Enumerator auf irgend eine Art bekannt gemacht werden. Ich bevorzuge in solchen Fällen den Konstruktor, da ich so nicht ein zusätzliches Attribut einführen muss. Der Compiler verhindert auch, dass ich vergesse, die Liste anzugeben wenn der Default-Konstruktor fehlt.
Zuerst aber wieder der Test, der das ganze nötig macht.
GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)
Der erste Test ist der schon vorher eingeführte Test, erweitert um den Parameter im Konstruktor. Der zweite Test ist der neue Test, welcher einen Zugriff auf die Liste verlangt. Im LinkedListEnumerator muss der Konstruktor und eine Variable für die Liste hinzugefügt werden und MoveNext wird angepasst:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class LinkedListEnumerator : IEnumerator { LinkedList list; public LinkedListEnumerator(LinkedList list) { this.list = list; } public bool MoveNext() { return list.Count > 0; } // (...) } |
GetEnumerator in der LinkedList muss auch noch angepasst werden, wir übergeben dem Konstruktor einfach this und schon kompiliert wieder alles. Und die Test sind auch wieder alle grün.
Den zweiten Test passe ich nun noch an, so dass eine vernünftige Implementation von MoveNext nötig wird.
GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)
Für die Implementierung sind noch ein paar Umbauarbeiten nötig, doch zuerst die neue Version von MoveNext:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | private LinkedListElement current; public bool MoveNext() { if(list.Count == 0) return false; if (current == null) { current = list.GetAt(0); return true; } current = current.Next; return current != null; } |
Neben der oben gezeigten neuen Membervariable in der Klasse LinkedListEnumerator verschieben wir auch die Klasse LinkedListElement, welche früher eine geschachtelte Klasse war, in den normalen Namespace und deklarieren sie als internal. Die Methode GetAt(int) deklarieren wir auch als internal, so dass der Enumerator sie verwenden kann.
Dank der Unit-Tests gehen wir bei diesen Umbauarbeiten kein Risiko ein, was wir auch jederzeit überprüfen können.
Und ja, ich habe wieder zu viel implementiert, also noch einen allgemeineren Test, um alle Abläufe durchzugehen.
GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)
Faster, Faster!
Den Rest des Enumerators erledigen wir im Schnellzugtempo. Ein paar Tests und nur noch seht wenig zu implementieren.
GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)
Und hier die dazugehörigen Implementationen. Für Dispose habe ich keinen Test geschrieben, da Dispose auch nichts macht. Wenn man mit Sicherheit verhindern will, dass noch die NotImplementedException geworfen wird, kann man einen Test schreiben, es würde aber spätestens auffallen, wenn man die Liste ein erstes mal mit foreach verwendet.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public T Current { get { if (current == null) { throw new InvalidOperationException(); } return current.Element; } } object System.Collections.IEnumerator.Current { get { return Current; } } public void Dispose() { } public void Reset() { current = null; } |
Die Tests benötigen noch ein Refactoring und der Konstruktor des Enumerators sollte nicht zugänglich sein, da nur die Liste den passenden Enumerator erstellen soll. Nach den Änderungen sieht dann die Testklasse wie folgt aus.
GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)
Den Enumerator haben wir nun, die restlichen Funktionen fehlen aber immer noch. Wir nehmen uns diesen im fünften Teil an.
Pingback: LinkedList Dojo mit Visual T# – Teil 3 « of bits and bytes
Pingback: LinkedList Dojo mit Visual T# – Teil 5 « of bits and bytes
Pingback: IList Dojo Retrospektive « of bits and bytes