LinkedList Dojo mit Visual T# – Teil 4

Karate

Image courtesy of „luigi diamanti“ / FreeDigitalPhotos.net


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:

1
2
3
4
5
6
7
8
9
10
11
12
13
test GetEnumerator()
{
    runtest System.Collections.Generic.IEnumerator enumerator = sut.GetEnumerator();
           
    assert enumerator != null;
}
               
test GetEnumerator()
{
    runtest System.Collections.IEnumerator enumerator = ((System.Collections.IEnumerable)sut).GetEnumerator();
           
    assert enumerator != null;
}

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace RolandBaer.LinkedListTests
{
  testclass for LinkedListEnumerator
  {
    test MoveNext()
    {
       LinkedListEnumerator enumerator = new LinkedListEnumerator();
       runtest bool res = enumerator.MoveNext() ;
       
       assert res == false;      
    }
  }
}

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
test MoveNext()
{
   LinkedListEnumerator enumerator = new LinkedListEnumerator(new LinkedList());
   runtest bool res = enumerator.MoveNext() ;
       
   assert res == false;      
}
       
test MoveNext()
{
   LinkedList list = new LinkedList();
   list.Add(5);
   LinkedListEnumerator enumerator = new LinkedListEnumerator(list);
   runtest bool res = enumerator.MoveNext() ;
       
   assert res == true;      
}

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
test MoveNext()
{
    bool res1, res2;
    LinkedList list = new LinkedList();
    list.Add(5);
    LinkedListEnumerator enumerator = new LinkedListEnumerator(list);
    runtest
    {
        res1 = enumerator.MoveNext() ;
        res2 = enumerator.MoveNext() ;
    }
 
    assert res1 == true;      
    assert res2 == false;      
}

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
test MoveNext()
{
    int count = 0;
    LinkedList list = new LinkedList();
    list.Add(5);
    list.Add(8);
    list.Add(2);
    list.Add(4);
    LinkedListEnumerator enumerator = new LinkedListEnumerator(list);
    runtest
    {
        while(enumerator.MoveNext())
            count++;
    }
       
    assert count == 4;      
}

Faster, Faster!

Den Rest des Enumerators erledigen wir im Schnellzugtempo. Ein paar Tests und nur noch seht wenig zu implementieren.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
test Current get
{
    int res1;
    int res2;
    LinkedList list = new LinkedList();
    list.Add(5);
    list.Add(8);
    list.Add(2);
    list.Add(4);
    LinkedListEnumerator enumerator = new LinkedListEnumerator(list);
    runtest
    {
        enumerator.MoveNext();
        res1 = enumerator.Current;
        enumerator.MoveNext();
        enumerator.MoveNext();
        res2 = enumerator.Current;
    }
       
    assert res1 == 5;      
    assert res2 == 2;      
}

test Current get
{
    int res1;
    int res2;
    LinkedList list = new LinkedList();
    list.Add(5);
    list.Add(8);
    list.Add(2);
    list.Add(4);
    System.Collections.IEnumerator enumerator = new LinkedListEnumerator(list);
    runtest
    {
        enumerator.MoveNext();
        res1 = (int)enumerator.Current;
        enumerator.MoveNext();
        enumerator.MoveNext();
        res2 = (int)enumerator.Current;
    }
     
    assert res1 == 5;      
    assert res2 == 2;      
}
       
test Current get
{
    LinkedList list = new LinkedList();
    LinkedListEnumerator enumerator = new LinkedListEnumerator(list);
    runtest int res = enumerator.Current;
   
    assert thrown InvalidOperationException;      
}
       
test Reset()
{
    int res1;
    int res2;
    LinkedList list = new LinkedList();
    list.Add(5);
    list.Add(8);
    list.Add(2);
    list.Add(4);
    System.Collections.IEnumerator enumerator = new LinkedListEnumerator(list);
    runtest
    {
        enumerator.MoveNext();
        res1 = (int)enumerator.Current;
        enumerator.Reset();
        enumerator.MoveNext();
        res2 = (int)enumerator.Current;
    }
     
    assert res1 == 5;      
    assert res2 == 5;      
}

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.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
testclass for LinkedListEnumerator
{
    LinkedList list;
   
    testcontext
    {
        test
        {
            list = new LinkedList ();
            list.Add(5);
            list.Add(8);
            list.Add(2);
            list.Add(4);
            runtest;
        }
    }
       
    test MoveNext()
    {
        System.Collections.Generic.IEnumerator enumerator = new LinkedList().GetEnumerator();
        runtest bool res = enumerator.MoveNext() ;
       
        assert res == false;      
    }
       
    test MoveNext()
    {
        bool res1, res2;
        LinkedList list = new LinkedList();
        list.Add(5);
        System.Collections.Generic.IEnumerator enumerator = list.GetEnumerator();
        runtest
        {
            res1 = enumerator.MoveNext() ;
            res2 = enumerator.MoveNext() ;
        }
       
        assert res1 == true;      
        assert res2 == false;      
    }
       
    test MoveNext()
    {
        int count = 0;
        System.Collections.Generic.IEnumerator enumerator = list.GetEnumerator();
        runtest
        {
            while(enumerator.MoveNext())
                count++;
        }
       
        assert count == 4;      
    }
       
    test Current get
    {
        int res1;
        int res2;
        System.Collections.Generic.IEnumerator enumerator = list.GetEnumerator();
        runtest
        {
            enumerator.MoveNext();
            res1 = enumerator.Current;
            enumerator.MoveNext();
            enumerator.MoveNext();
            res2 = enumerator.Current;
        }
       
        assert res1 == 5;      
        assert res2 == 2;      
    }

    test Current get
    {
        int res1;
        int res2;
        System.Collections.IEnumerator enumerator = list.GetEnumerator();
        runtest
        {
            enumerator.MoveNext();
            res1 = (int)enumerator.Current;
            enumerator.MoveNext();
            enumerator.MoveNext();
            res2 = (int)enumerator.Current;
        }
     
        assert res1 == 5;      
        assert res2 == 2;      
    }
       
    test Current get
    {
        LinkedList list = new LinkedList();
        System.Collections.Generic.IEnumerator enumerator = list.GetEnumerator();
        runtest int res = enumerator.Current;
   
        assert thrown InvalidOperationException;      
    }
       
    test Reset()
    {
        int res1;
        int res2;
        System.Collections.IEnumerator enumerator = list.GetEnumerator();
        runtest
        {
            enumerator.MoveNext();
            res1 = (int)enumerator.Current;
            enumerator.Reset();
            enumerator.MoveNext();
            res2 = (int)enumerator.Current;
        }
     
        assert res1 == 5;      
        assert res2 == 5;      
    }    
}

Den Enumerator haben wir nun, die restlichen Funktionen fehlen aber immer noch. Wir nehmen uns diesen im fünften Teil an.

3 Gedanken zu „LinkedList Dojo mit Visual T# – Teil 4

  1. Pingback: LinkedList Dojo mit Visual T# – Teil 3 « of bits and bytes

  2. Pingback: LinkedList Dojo mit Visual T# – Teil 5 « of bits and bytes

  3. Pingback: IList Dojo Retrospektive « of bits and bytes

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.