Visual Basic 2012 Voorbeelden
   

visual basic 2012 broncode voorbeelden

Blijf op de hoogte van de recente aanpassingen op vbvoorbeelden!

Microsoft Visual Studio 2012Microsoft Developers Network - Visual BasicMicrosoft .NET Framework

22.1. Enumerable Collecties - IEnumerable en IEnumerator Interfaces

Print Email Deel op Twitter Deel op Facebook

Dit artikel is gepubliceerd op maandag 15 oktober 2012 op vbvoorbeelden, bezoek de website voor een recente versie van dit artikel of andere artikels.

In de Framework Class Library ("FCL") is een Queue klasse voorzien om elementen van eender welke type (elementtype Object) aan toe te voegen en uit te verwijderen volgens het "FIFO" principe.
Visual Basic 2012 Broncode - Codevoorbeeld 486
Option Strict On
Option Explicit On
Namespace Example1
    Class Client1
        Public Shared Sub Main()
            Dim queue1 As New System.Collections.Queue
            Console.WriteLine(queue1.Count)
            '
            queue1.Enqueue(1)
            queue1.Enqueue(2)
            queue1.Enqueue(3)
            Console.WriteLine(queue1.Count)
            '
            Console.WriteLine(queue1.Peek())
            '
            queue1.Dequeue()
            Console.WriteLine(queue1.Count)
            '
            Console.WriteLine(queue1.Peek())
            '
            queue1.Dequeue()
            Console.WriteLine(queue1.Count)
            '
            Console.WriteLine(queue1.Peek())
            '
            queue1.Dequeue()
            Console.WriteLine(queue1.Count)
            '
            queue1.Enqueue(1)
            queue1.Enqueue(2)
            queue1.Enqueue(3)
            Console.WriteLine(queue1.Count)
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
0
3
1
2
2
1
3
0
3
Om één en ander te illustreren gaan we eens zelf een eenvoudige versie schrijven van een queue collectie :
Visual Basic 2012 Broncode - Codevoorbeeld 487
Namespace Example1
    Class Queue
        Private _Items As Object() = {}
        Public ReadOnly Property Count() As Integer
            Get
                Count = _Items.Length
            End Get
        End Property
        Public Sub Enqueue(ByVal value As Object)
            ReDim Preserve _Items(Count)
            _Items(Count - 1) = value
        End Sub
        Public Function Peek() As Object
            Peek = _Items(0)
        End Function
        Public Sub Dequeue()
            For index As Integer = 0 To Count - 2
                _Items(index) = _Items(index + 1)
            Next
            ReDim Preserve _Items(Count - 2)
        End Sub
    End Class
End Namespace
De klasse Queue maakt intern gebruik van een Object() om de elementen in te verzamelen.

Zoals onderstaande Client demonstreert hebben we met onze nieuwe Queue objecten nu min of meer dezelfde mogelijkheden :
Visual Basic 2012 Broncode - Codevoorbeeld 488
Namespace Example1
    Class Client2
        Public Shared Sub Main()
            Dim queue1 As New Queue
            Console.WriteLine(queue1.Count)
            '
            queue1.Enqueue(1)
            queue1.Enqueue(2)
            queue1.Enqueue(3)
            Console.WriteLine(queue1.Count)
            '
            Console.WriteLine(queue1.Peek())
            '
            queue1.Dequeue()
            Console.WriteLine(queue1.Count)
            '
            Console.WriteLine(queue1.Peek())
            '
            queue1.Dequeue()
            Console.WriteLine(queue1.Count)
            '
            Console.WriteLine(queue1.Peek())
            '
            queue1.Dequeue()
            Console.WriteLine(queue1.Count)
            '
            queue1.Enqueue(1)
            queue1.Enqueue(2)
            queue1.Enqueue(3)
            Console.WriteLine(queue1.Count)
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
0
3
1
2
2
1
3
0
3

22.1.1. Vereiste Members voor het Enumereren van een Collectie

Wat niet met onze eigen Queue, maar wel met de reeds voorgedefinieerde Queue mogelijk is, is om op eenvoudige wijze over de elementen te itereren, om zo de elementen van deze collectie te benaderen (ook wel "enumereren" genoemd).
Dit bijvoorbeeld via een For Each ... Next iteratie.
Visual Basic 2012 Broncode - Codevoorbeeld 489
Namespace Example1
    Class Client3
        Public Shared Sub Main()
            Dim queue1 As New System.Collections.Queue
            queue1.Enqueue(1)
            queue1.Enqueue(2)
            queue1.Enqueue(3)
            '
            For Each element As Object In queue1
                Console.Write(element.ToString() & " ")
            Next
            Console.WriteLine()
            '
            For Each element As Object In queue1
                Console.Write(element.ToString() & " ")
            Next
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
1 2 3
1 2 3
Bovenstaande Client itereert twee maal over de elementen van de collectie, en benaderd deze elementen om ze op de console af te drukken.

In onze eigen Queue werd geen doorloopmechanisme voorzien, om op eenvoudige wijze over de elementen van collectie te enumereren, en zo de elementen te benaderen.

Als we toch dergelijk mechanisme wensen te creëren voor onze Queue objecten, dan zouden we dit kunnen doen door enkele extra members in de interface (enkele extra publieke members) van deze collectiestructuur te voorzien.

Bijvoorbeeld zou men in een client als volgt te werk kunnen gaan om over de elementen te enumereren :
Visual Basic 2012 Broncode - Codevoorbeeld 490
Namespace Example1
    Class Client4
        Public Shared Sub Main()
            Dim queue1 As New Queue
            queue1.Enqueue(1)
            queue1.Enqueue(2)
            queue1.Enqueue(3)
            '
            Do While queue1.HasNext()
                queue1.MoveNext()
                Console.Write(queue1.Current.ToString() & " ")
            Loop
            queue1.Reset()
            Console.WriteLine()
            '
            Do While queue1.HasNext()
                queue1.MoveNext()
                Console.Write(queue1.Current.ToString() & " ")
            Loop
            queue1.Reset()
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
1 2 3
1 2 3
Waarbij :
- HasNext aangeeft of er een volgend element is.
- MoveNext ervoor zorgt dat de volgende keer Current weldegelijk het volgende element oplevert.
- Current het huidige element oplevert.
- Reset ervoor zorgt dat bij een volgende doorloping er opnieuw bij het begin van de collectie wordt begonnen.  Dit is nodig om tussen twee doorloping door, de iteratielogica te kunnen "resetten".

Ook hier wordt dus twee maal over de elementen van de collectie geënumereerd, om de elementen op de console af te drukken.

Om onze Queue hierop te voorzien, kunnen we de klasse met deze members en een bijpassende implementatie uitbreiden :
Visual Basic 2012 Broncode - Codevoorbeeld 491
Namespace Example1
    Partial Class Queue
        Private _Cursor As Integer = -1
        Public Function HasNext() As Boolean
            Dim cursorNextElement As Integer = _Cursor + 1
            HasNext = (cursorNextElement <= _Items.GetUpperBound(0))
        End Function
        Public Sub MoveNext()
            _Cursor += 1
        End Sub
        Public ReadOnly Property Current() As Object
            Get
                Current = _Items(_Cursor)
            End Get
        End Property
        Public Sub Reset()
            _Cursor = -1
        End Sub
    End Class
End Namespace
Hierbij zijn nu de nodige voorzieningen aangebracht om het mogelijk te maken op eenvoudige wijze over de elementen van de collectie te enumereren.

Ook in andere user defined collecties zouden we dezelfde members en implementatie kunnen opnemen, om op eenvoudige wijze over de elementen van de collectie te enumereren.

Hieronder is een eenvoudige LinkedList gecreëerd :
Visual Basic 2012 Broncode - Codevoorbeeld 492
Namespace Example1
    Class LinkedList
        Private _Sentinel As Node = New Node(0)
        Private _Tail As Node = _Sentinel
        Public Sub Add(ByVal value As Object)
            _Tail.NextNode = New Node(value)
            _Tail = _Tail.NextNode
        End Sub
        Public Class Node
            Public NextNode As Node
            Public Value As Object
            Public Sub New(ByVal value As Object)
                Me.Value = value
            End Sub
            Public Overrides Function ToString() As String
                ToString = Value.ToString()
            End Function
        End Class
    End Class
End Namespace
Dezelfde members (met gelijkaardige implementatie) kunnen aan de publieke interface van LinkedList worden toegevoegd om het doorlopen van de elementen mogelijk te maken :
Visual Basic 2012 Broncode - Codevoorbeeld 493
Namespace Example1
    Partial Class LinkedList
        Private _Cursor As Node = _Sentinel
        Public Function HasNext() As Boolean
            Dim cursorNextElement As Node = _Cursor.NextNode
            HasNext = (cursorNextElement IsNot Nothing)
        End Function
        Public Sub MoveNext()
            _Cursor = _Cursor.NextNode
        End Sub
        Public ReadOnly Property Current() As Object
            Get
                Current = _Cursor.Value
            End Get
        End Property
        Public Sub Reset()
            _Cursor = _Sentinel
        End Sub
    End Class
End Namespace
Nu is het ook met LinkedList objecten mogelijk om op eenvoudige wijze over de elementen van deze collectie te enumereren :
Visual Basic 2012 Broncode - Codevoorbeeld 494
Namespace Example1
    Class Client5
        Public Shared Sub Main()
            Dim linkedList1 As New LinkedList
            linkedList1.Add(1)
            linkedList1.Add(2)
            linkedList1.Add(3)
            '
            Do While linkedList1.HasNext()
                linkedList1.MoveNext()
                Console.Write(linkedList1.Current.ToString() & " ")
            Loop
            linkedList1.Reset()
            Console.WriteLine()
            '
            Do While linkedList1.HasNext()
                linkedList1.MoveNext()
                Console.Write(linkedList1.Current.ToString() & " ")
            Loop
            linkedList1.Reset()
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
1 2 3
1 2 3
We hebben hier nu geïllustreerd hoe members als Current, HasNext, MoveNext en Reset nuttige zijn in de publieke interface van elk collectietype waar we op eenvoudige wijze over de elementen willen enumereren.

22.1.2. Enumerable Interface

We kunnen hiervoor bijgevolg ook een interface voor definiëren.  Het kan immers nuttig zijn om alle itereerbare collecties in dezelfde context te gaan gebruiken.

De interface zou er zo kunnen uitzien :
Visual Basic 2012 Broncode - Codevoorbeeld 495
Namespace Example2
    Interface IEnumerable
        Function HasNext() As Boolean
        Sub MoveNext()
        ReadOnly Property Current() As Object
        Sub Reset()
    End Interface
End Namespace
Een basisklasse, in tegenstelling tot een interface, had hier niet toepasselijk geweest, gezien elk collectie type een andere implementatie voor de doorloop members nodig heeft.
Onze Queue en LinkedList gaan immers op totaal verschillende manier de members HasNext, MoveNext en Current implementeren.

Onze Queue en List kunnen nu deze interface implementeren, waardoor beide types garanderen een gedrag te definiëren voor het doorlopen van de elementen :
Visual Basic 2012 Broncode - Codevoorbeeld 496
Namespace Example2
    Class Queue : Implements IEnumerable
        Private _Items As Object() = {}
        Public ReadOnly Property Count() As Integer
            Get
                Count = _Items.Length
            End Get
        End Property
        Public Sub Enqueue(ByVal value As Object)
            ReDim Preserve _Items(Count)
            _Items(Count - 1) = value
        End Sub
        Public Function Peek() As Object
            Peek = _Items(0)
        End Function
        Public Sub Dequeue()
            For index As Integer = 0 To Count - 2
                _Items(index) = _Items(index + 1)
            Next
            ReDim Preserve _Items(Count - 2)
        End Sub
        Private _Cursor As Integer = -1
        Public Function HasNext() As Boolean _
                                           Implements IEnumerable.HasNext
            Dim cursorNextElement As Integer = _Cursor + 1
            HasNext = (cursorNextElement <= _Items.GetUpperBound(0))
        End Function
        Public Sub MoveNext() Implements IEnumerable.MoveNext
            _Cursor += 1
        End Sub
        Public ReadOnly Property Current() As Object _
                                           Implements IEnumerable.Current
            Get
                Current = _Items(_Cursor)
            End Get
        End Property
        Public Sub Reset() Implements IEnumerable.Reset
            _Cursor = -1
        End Sub
    End Class
    Class LinkedList : Implements IEnumerable
        Private _Sentinel As Node = New Node(0)
        Private _Tail As Node = _Sentinel
        Public Sub Add(ByVal value As Object)
            _Tail.NextNode = New Node(value)
            _Tail = _Tail.NextNode
        End Sub
        Public Class Node
            Public NextNode As Node
            Public Value As Object
            Public Sub New(ByVal value As Object)
                Me.Value = value
            End Sub
            Public Overrides Function ToString() As String
                ToString = Value.ToString()
            End Function
        End Class
        Private _Cursor As Node = _Sentinel
        Public Function HasNext() As Boolean _
                                           Implements IEnumerable.HasNext
            Dim cursorNextElement As Node = _Cursor.NextNode
            HasNext = (cursorNextElement IsNot Nothing)
        End Function
        Public Sub MoveNext() Implements IEnumerable.MoveNext
            _Cursor = _Cursor.NextNode
        End Sub
        Public ReadOnly Property Current() As Object _
                                           Implements IEnumerable.Current
            Get
                Current = _Cursor.Value
            End Get
        End Property
        Public Sub Reset() Implements IEnumerable.Reset
            _Cursor = _Sentinel
        End Sub
    End Class
End Namespace
Alle types die IEnumerable implementeren kunnen nu in dezelfde context worden gebruikt.
Een method als onderstaande Print zal de elementen afdrukken van de IEnumerable argumentwaarde :
Visual Basic 2012 Broncode - Codevoorbeeld 497
Namespace Example2
    Class Client1
        Public Shared Sub Main()
            Dim queue1 As New Queue
            queue1.Enqueue(1)
            queue1.Enqueue(2)
            queue1.Enqueue(3)
            '
            Print(queue1)
            Print(queue1)
            '
            Dim linkedList1 As New LinkedList
            linkedList1.Add(1)
            linkedList1.Add(2)
            linkedList1.Add(3)
            '
            Print(linkedList1)
            Print(linkedList1)
            '
            Console.ReadLine()
        End Sub
        Public Shared Sub Print(ByVal aCollection As IEnumerable)
            Do While aCollection.HasNext()
                aCollection.MoveNext()
                Console.Write(aCollection.Current.ToString() & " ")
            Loop
            aCollection.Reset()
            Console.WriteLine()
        End Sub
    End Class
End Namespace
Console Application Output
1 2 3
1 2 3
1 2 3
1 2 3
Toch zijn we nog altijd beperkt in de iteratie mogelijkheden die onze iteratiemembers (uit de IEnumerable interface) ons opleveren.

22.1.3. Enumerator

Stel in onderstaande Client levert de functie GetQueueAverage het gemiddelde op.  Deze GetQueueAverage wordt gebruikt in onze PrintQueue om niet alleen de elementwaarde die benaderd wordt in de iteratie af te drukken, maar ook af te drukken hoe deze elementwaarde zich positioneert ten aanzien van het gemiddelde ("> average", "< average" of "= average").

Als je deze onderstaande Client bekijkt dan zul je daar waarschijnlijk niet meteen een probleem in zien.  Toch loopt het fout bij de uitvoer ( IndexOutOfRangeException) :
Visual Basic 2012 Broncode - Codevoorbeeld 498
Namespace Example2
    Class Client2
        Public Shared Sub Main()
            Dim queue1 As New Queue
            queue1.Enqueue(1)
            queue1.Enqueue(2)
            queue1.Enqueue(3)
            '
            PrintQueue(queue1)
            '
            Console.ReadLine()
        End Sub
        Public Shared Sub PrintQueue(ByVal aQueue As Queue)
            Do While aQueue.HasNext()
                aQueue.MoveNext()
                Console.Write(aQueue.Current.ToString() & " ")
                Dim average As Integer = GetQueueAverage(aQueue)
                Select Case Convert.ToInt32(aQueue.Current())
                    Case Is > average
                        Console.Write("( > " & average & ") ")
                    Case Is < average
                        Console.Write("( < " & average & ") ")
                    Case Is = average
                        Console.Write("( = " & average & ") ")
                End Select
            Loop
            aQueue.Reset()
            Console.WriteLine()
        End Sub
        Public Shared Function GetQueueAverage(ByVal aQueue As Queue) As Integer
            Dim counter As Integer
            Do While aQueue.HasNext()
                aQueue.MoveNext()
                GetQueueAverage += Convert.ToInt32(aQueue.Current)
                counter += 1
            Loop
            aQueue.Reset()
            GetQueueAverage = Convert.ToInt32(GetQueueAverage / counter)
        End Function
    End Class
End Namespace
Het probleem zit hier in het feit dat we tijdens het enumereren over de elementen om ze af te drukken, ook nog eens over enumereren om het gemiddelde te bepalen.  Maar beide doorlopingen werken met dezelfde "cursor", waardoor onze doorlopingen elkaar beïnvloeden en een foutief resultaat veroorzaken.

De essentie van het probleem is dat het collectie object zelf, maar over één cursor beschikt, en dus nooit meer dan één doorloping correct op hetzelfde moment kan laten uitvoeren.

Dit oplossen kan door de doorlooplogica (inclusief alle members en hun implementaties) uit de collectie te halen, en in een aparte "enumerator" onder te brengen.
Als we beschikken over dergelijke enumerator (met daarin de nodige members voorzien om over de collectie te enumereren), die zelf als toestand bijhoudt welke de huidige "cursor" positie is, zullen verschillende doorlopingen (die op verschillende enumerators gebeuren) elkaar niet meer beïnvloeden.

Gezien de enumerator dan afgescheiden wordt van de collectie (waarover we moeten kunnen enumereren) moet bij elke doorloping (die op de collectie moet gebeuren) eerst de enumerator van die collectie worden opgevraagd.

Een constructie voor een enumereerbare Queue zou er dan als volgt kunnen uitzien :
Visual Basic 2012 Broncode - Codevoorbeeld 499
Namespace Example3
    Class Queue
        Private _Items As Object() = {}
        Public ReadOnly Property Count() As Integer
            Get
                Count = _Items.Length
            End Get
        End Property
        Public Sub Enqueue(ByVal value As Object)
            ReDim Preserve _Items(Count)
            _Items(Count - 1) = value
        End Sub
        Public Function Peek() As Object
            Peek = _Items(0)
        End Function
        Public Sub Dequeue()
            For index As Integer = 0 To Count - 2
                _Items(index) = _Items(index + 1)
            Next
            ReDim Preserve _Items(Count - 2)
        End Sub
        Public Function GetEnumerator() As QueueEnumerator
            GetEnumerator = New QueueEnumerator(_Items)
        End Function
    End Class
    Class QueueEnumerator
        Private _Items As Object() = {}
        Private _Cursor As Integer = -1
        Public Sub New(ByVal items As Object())
            _Items = items
        End Sub
        Public Function HasNext() As Boolean
            Dim cursorNextElement As Integer = _Cursor + 1
            HasNext = (cursorNextElement <= _Items.GetUpperBound(0))
        End Function
        Public Sub MoveNext()
            _Cursor += 1
        End Sub
        Public ReadOnly Property Current() As Object
            Get
                Current = _Items(_Cursor)
            End Get
        End Property
        Public Sub Reset()
            _Cursor = -1
        End Sub
    End Class
End Namespace
Aan de constructor van de QueueEnumerator wordt de referentie doorgegeven van de interne Object() van de Queue collectie zelf.  Ook de iterator moet immers beschikken over de interne collectie, anders kan bijvoorbeeld de member Current nooit het huidige element uit die interne collectie opleveren.

In onderstaande client gaan we nu opnieuw (net zoals in vorig voorbeelden) verschillende doorloping op het zelfde moment laten uitvoeren :
Visual Basic 2012 Broncode - Codevoorbeeld 500
Namespace Example3
    Class Client
        Public Shared Sub Main()
            Dim queue1 As New Queue
            queue1.Enqueue(1)
            queue1.Enqueue(2)
            queue1.Enqueue(3)
            '
            PrintQueue(queue1)
            '
            Console.ReadLine()
        End Sub
        Public Shared Sub PrintQueue(ByVal aQueue As Queue)
            Dim aQueueEnumerator As QueueEnumerator = aQueue.GetEnumerator()
            Do While aQueueEnumerator.HasNext()
                aQueueEnumerator.MoveNext()
                Console.Write(aQueueEnumerator.Current.ToString() & " ")
                Dim average As Integer = GetQueueAverage(aQueue)
                Select Case Convert.ToInt32(aQueueEnumerator.Current())
                    Case Is > average
                        Console.Write("( > " & average & ") ")
                    Case Is < average
                        Console.Write("( < " & average & ") ")
                    Case Is = average
                        Console.Write("( = " & average & ") ")
                End Select
            Loop
            aQueueEnumerator.Reset()
            Console.WriteLine()
        End Sub
        Public Shared Function GetQueueAverage(ByVal aQueue As Queue) As Integer
            Dim aQueueEnumerator As QueueEnumerator = aQueue.GetEnumerator()
            Dim counter As Integer
            Do While aQueueEnumerator.HasNext()
                aQueueEnumerator.MoveNext()
                GetQueueAverage += Convert.ToInt32(aQueueEnumerator.Current)
                counter += 1
            Loop
            aQueueEnumerator.Reset()
            GetQueueAverage = Convert.ToInt32(GetQueueAverage / counter)
        End Function
    End Class
End Namespace
Console Application Output
1 (< 2) 2 (= 2) 3 (> 2)
Deze keer gebeuren de doorlopingen op een door de collectie opgeleverde enumerator, in plaats van op de collectie zelf.  Elke enumerator (en dus ook elke doorloping) kan hier zelf zijn eigen "cursor" beheren.  De verschillende doorlopingen worden dus niet door elkaar beïnvloed.

Alles wat enumereerbaar is moet nu niet meer rechtstreeks beschikken over de members HasNext, MoveNext, Current en Reset, maar moet beschikken over een enumerator :
Visual Basic 2012 Broncode - Codevoorbeeld 501
Namespace Example4
    Interface IEnumerable
        Function GetEnumerator() As IEnumerator
    End Interface
    Interface IEnumerator
        Function HasNext() As Boolean
        Sub MoveNext()
        ReadOnly Property Current() As Object
        Sub Reset()
    End Interface
End Namespace
Onderstaand voorbeeld illustreert hoe Queue en LinkedList de interface IEnumerable kunnen implementeren :
Visual Basic 2012 Broncode - Codevoorbeeld 502
Namespace Example4
    Class Queue : Implements IEnumerable
        Private _Items As Object() = {}
        Public ReadOnly Property Count() As Integer
            Get
                Count = _Items.Length
            End Get
        End Property
        Public Sub Enqueue(ByVal value As Object)
            ReDim Preserve _Items(Count)
            _Items(Count - 1) = value
        End Sub
        Public Function Peek() As Object
            Peek = _Items(0)
        End Function
        Public Sub Dequeue()
            For index As Integer = 0 To Count - 2
                _Items(index) = _Items(index + 1)
            Next
            ReDim Preserve _Items(Count - 2)
        End Sub
        Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
            GetEnumerator = New QueueEnumerator(_Items)
        End Function
    End Class
    Class QueueEnumerator : Implements IEnumerator
        Private _Items As Object()
        Private _Cursor As Integer = -1
        Public Sub New(ByVal items As Object())
            _Items = items
        End Sub
        Public Function HasNext() As Boolean Implements IEnumerator.HasNext
            Dim cursorNextElement As Integer = _Cursor + 1
            HasNext = (cursorNextElement <= _Items.GetUpperBound(0))
        End Function
        Public Sub MoveNext() Implements IEnumerator.MoveNext
            _Cursor += 1
        End Sub
        Public ReadOnly Property Current() As Object Implements IEnumerator.Current
            Get
                Current = _Items(_Cursor)
            End Get
        End Property
        Public Sub Reset() Implements IEnumerator.Reset
            _Cursor = -1
        End Sub
    End Class
    Class LinkedList : Implements IEnumerable
        Private _Sentinel As Node = New Node(0)
        Private _Tail As Node = _Sentinel
        Public Sub Add(ByVal value As Object)
            _Tail.NextNode = New Node(value)
            _Tail = _Tail.NextNode
        End Sub
        Public Class Node
            Public NextNode As Node
            Public Value As Object
            Public Sub New(ByVal value As Object)
                Me.Value = value
            End Sub
            Public Overrides Function ToString() As String
                ToString = Value.ToString()
            End Function
        End Class
        Public Function GetEnumerator() As IEnumerator _
                                            Implements IEnumerable.GetEnumerator
            GetEnumerator = New LinkedListEnumerator(_Sentinel)
        End Function
    End Class
    Class LinkedListEnumerator : Implements IEnumerator
        Private _Sentinel As LinkedList.Node
        Private _Cursor As LinkedList.Node
        Public Sub New(ByVal sentinel As LinkedList.Node)
            _Sentinel = sentinel
            _Cursor = _Sentinel
        End Sub
        Public Function HasNext() As Boolean Implements IEnumerator.HasNext
            Dim cursorNextElement As LinkedList.Node = _Cursor.NextNode
            HasNext = (cursorNextElement IsNot Nothing)
        End Function
        Public Sub MoveNext() Implements IEnumerator.MoveNext
            _Cursor = _Cursor.NextNode
        End Sub
        Public ReadOnly Property Current() As Object Implements IEnumerator.Current
            Get
                Current = _Cursor.Value
            End Get
        End Property
        Public Sub Reset() Implements IEnumerator.Reset
            _Cursor = _Sentinel
        End Sub
    End Class
End Namespace
In onderstaand Client kunnen we nu op eenvoudige wijze over de elementen van een LinkedList of Queue object enumereren :
Visual Basic 2012 Broncode - Codevoorbeeld 503
Namespace Example4
    Class Client1
        Public Shared Sub Main()
            Dim queue1 As New Queue
            queue1.Enqueue(1)
            queue1.Enqueue(2)
            queue1.Enqueue(3)
            '
            Print(queue1)
            Print(queue1)
            '
            Dim linkedList1 As New LinkedList
            linkedList1.Add(1)
            linkedList1.Add(2)
            linkedList1.Add(3)
            '
            Print(linkedList1)
            Print(linkedList1)
            '
            Console.ReadLine()
        End Sub
        Public Shared Sub Print(ByVal aCollection As IEnumerable)
            Dim aEnumerator As IEnumerator = aCollection.GetEnumerator()
            Do While aEnumerator.HasNext()
                aEnumerator.MoveNext()
                Console.Write(aEnumerator.Current.ToString() & " ")
            Loop
            aEnumerator.Reset()
            Console.WriteLine()
        End Sub
    End Class
End Namespace
Console Application Output
1 2 3
1 2 3
1 2 3
1 2 3

22.1.4. System.Collections.IEnumerable en IEnumerator

Interfaces als IEnumerable en IEnumerator zoals wij ze in vorige voorbeelden hadden gedefinieerd :
Interface IEnumerable
    Function GetEnumerator() As IEnumerator
End Interface
Interface IEnumerator
    Function HasNext() As Boolean
    Sub MoveNext()
    ReadOnly Property Current() As Object
    Sub Reset()
End Interface
Hadden we eigenlijk niet hoeven creëren, in de System.Collections namespace bevinden zich reeds vergelijkbare interfaces :
Interface IEnumerable
    Function GetEnumerator() As IEnumerator
End Interface
Interface IEnumerator
    Function MoveNext() As Boolean
    ReadOnly Property Current() As Object
    Sub Reset()
End Interface
Het enigste verschil is dat de implementatie van MoveNext bij deze voorgedefinieerde interface zowel naar het volgende element navigeert ( wat onze MoveNext deed), als aangeeft of er nog wel een volgend element is (wat onze HasNext deed).

In plaats van zelf interfaces hiervoor te definiëren, kunnen we beter de hiervoor reeds voorziene interface gebruiken.

Laten we eens onze user-defined Persons collectie enumereerbaar maken :
Visual Basic 2012 Broncode - Codevoorbeeld 504
Namespace Example5
    Class Person
        Public Sub New(ByVal name As String)
            Me.Name = name
        End Sub
        Public Property Name As String
        Public Overrides Function ToString() As String
            ToString = Name
        End Function
    End Class
    Class Persons : Implements System.Collections.IEnumerable
        Private _Items As Person() = {}
        Public ReadOnly Property Count() As Integer
            Get
                Count = _Items.GetLength(0)
            End Get
        End Property
        Public Sub Add(ByVal item As Person)
            ReDim Preserve _Items(Count)
            _Items(Count - 1) = item
        End Sub
        '
        Public Function GetEnumerator() As System.Collections.IEnumerator _
                         Implements System.Collections.IEnumerable.GetEnumerator
            GetEnumerator = New PersonsEnumerator(_Items)
        End Function
        '
        Private Class PersonsEnumerator
            Implements System.Collections.IEnumerator
            Private _Items As Person()
            Private _Cursor As Integer = -1
            Public Sub New(ByVal items As Person())
                _Items = items
            End Sub
            Public Function MoveNext() As Boolean _
                              Implements System.Collections.IEnumerator.MoveNext
                _Cursor += 1
                MoveNext = (_Cursor <= _Items.GetUpperBound(0))
            End Function
            Public ReadOnly Property Current() As Object _
                               Implements System.Collections.IEnumerator.Current
                Get
                    Current = _Items(_Cursor)
                End Get
            End Property
            Public Sub Reset() Implements System.Collections.IEnumerator.Reset
                _Cursor = -1
            End Sub
        End Class
    End Class
End Namespace
Zoals onderstaande Client demonstreert is het nu mogelijk om over de elementen van Persons object te enumereren :
Visual Basic 2012 Broncode - Codevoorbeeld 505
Namespace Example5
    Class Client1
        Public Shared Sub Main()
            Dim persons1 As New Persons
            persons1.Add(New Person("John"))
            persons1.Add(New Person("Jane"))
            '
            Dim aEnumerator As IEnumerator = persons1.GetEnumerator()
            Do While aEnumerator.MoveNext()
                Console.Write(aEnumerator.Current.ToString() & " ")
            Loop
            aEnumerator.Reset()
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
John Jane
De PersonsEnumerator klasse mag (maar hoeft niet) ingekapseld worden in de klasse Persons.  Clients van Persons hoeven geen rechtstreekse toegang te hebben tot tot deze enumerator.  De clients zullen voor het enumereren over de elementen van een Persons object immers enkel gebruik maken van de opgeleverde IEnumerator.

In plaats van manueel de enumerator van de collectie op te vragen, en dan manueel de IEnumerator members te gebruiken om over de enumerator te enumereren, kunnen we dit werk ook overlaten aan de For Each ... Next iteratie, die op de achtergrond toch zo vertaald wordt.
Visual Basic 2012 Broncode - Codevoorbeeld 506
Namespace Example5
    Class Client2
        Public Shared Sub Main()
            Dim persons1 As New Persons
            persons1.Add(New Person("John"))
            persons1.Add(New Person("Jane"))
            '
            For Each element As Person In persons1                         ' (1)
                Console.Write(element.ToString() & " ")
            Next
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
John Jane
Visual Studio : Plaats een breakpoint op regel (1) en voer de code uit, als je stap per stap verder gaat, zul je zien hoe deze For Each eigenlijk gebruik maakt van de members van de IEnumerable en IEnumerator implementaties.
De For Each ... Next vereist dus weliswaar dat je een IEnumerable (een "enumereerbare") collectie in de In clausule vermeldt.

De implementatie van de GetEnumerator() functie uit het interfacetype IEnumerable voor de klasse Persons kan nog vereenvoudigd worden.

De interne opslagstructuur die gebruikt wordt in Persons is een Person().  Wat we ondertussen ook weten is dat het type Array waarvan Person() is afgeleid reeds IEnumerable is, en deze dus reeds over een IEnumerator beschikt.

In plaats van zelf een IEnumerator type te definiëren kunnen we nu ook simpelweg de enumerator van onze interne array opleveren.
Visual Basic 2012 Broncode - Codevoorbeeld 507
Namespace Example6
    Class Persons : Implements System.Collections.IEnumerable
        Private _Items As Example5.Person() = {}
        Public ReadOnly Property Count() As Integer
            Get
                Count = _Items.GetLength(0)
            End Get
        End Property
        Public Sub Add(ByVal item As Example5.Person)
            ReDim Preserve _Items(Count)
            _Items(Count - 1) = item
        End Sub
        Default Public Property Item(ByVal index As Integer) As Example5.Person
            Get
                Item = _Items(index)
            End Get
            Set(ByVal value As Example5.Person)
                _Items(index) = value
            End Set
        End Property
        '
        Public Function GetEnumerator() As System.Collections.IEnumerator _
                         Implements System.Collections.IEnumerable.GetEnumerator
            GetEnumerator = _Items.GetEnumerator()
        End Function
    End Class
End Namespace
Een eigen IEnumerator implementatie zoals PersonsEnumerator is hier nu niet meer noodzakelijk.
Visual Basic 2012 Broncode - Codevoorbeeld 508
Namespace Example6
    Class Client
        Public Shared Sub Main()
            Dim persons1 As New Persons
            persons1.Add(New Example5.Person("John"))
            persons1.Add(New Example5.Person("Jane"))
            '
            For Each element As Example5.Person In persons1
                Console.Write(element.ToString() & " ")
            Next
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
John Jane
Ook indien we onze Persons collectie intern van een ArrayList laten gebruikmaken, kunnen we in de IEnumerable.GetEnumerator implementatie de enumerator van deze Arraylist opleveren :
Visual Basic 2012 Broncode - Codevoorbeeld 509
Namespace Example7
    Class Persons : Implements System.Collections.IEnumerable
        Private _Items As New ArrayList
        Public ReadOnly Property Count() As Integer
            Get
                Count = _Items.Count
            End Get
        End Property
        Public Sub Add(ByVal item As Example5.Person)
            _Items.Add(item)
        End Sub
        Default Public Property Item(ByVal index As Integer) As Example5.Person
            Get
                Item = DirectCast(_Items.Item(index), Example5.Person)
            End Get
            Set(ByVal value As Example5.Person)
                _Items.Item(index) = value
            End Set
        End Property
        '
        Public Function GetEnumerator() As System.Collections.IEnumerator _
                         Implements System.Collections.IEnumerable.GetEnumerator
            GetEnumerator = _Items.GetEnumerator()
        End Function
    End Class
    Class Client
        Public Shared Sub Main()
            Dim persons1 As New Persons
            persons1.Add(New Example5.Person("John"))
            persons1.Add(New Example5.Person("Jane"))
            '
            For Each element As Example5.Person In persons1
                Console.Write(element.ToString() & " ")
            Next
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
John Jane

22.1.5. Oefening

Opgave :

Creëer een BoundedStack type waarbij de elementen worden toegevoegd en verwijderd volgens het stack principe, maar waarbij het aantal elementen tot een bepaalde capaciteit begrenst is.

Maak deze BoundedStack IEnumerable en creëer ook een eigen IEnumerator.

Oplossing :
Visual Basic 2012 Broncode - Codevoorbeeld 510
Namespace Exercise
    Class BoundedStack : Implements IEnumerable
        Protected _Items As Object()
        Protected _Count As Integer
        Public Sub New(ByVal capacity As Integer)
            ReDim _Items(capacity - 1)
        End Sub
        Public ReadOnly Property Count() As Integer
            Get
                Count = _Count
            End Get
        End Property
        Public ReadOnly Property Capacity() As Integer
            Get
                Capacity = _Items.Length
            End Get
        End Property
        Public Sub Push(ByVal value As Object)
            If Count < Capacity Then
                _Items(Count) = value
                _Count += 1
            End If
        End Sub
        Public Function Peek() As Object
            If Count > 0 Then Peek = _Items(Count - 1)
        End Function
        Public Sub Pop()
            If Count > 0 Then
                _Items(Count - 1) = 0
                _Count -= 1
            End If
        End Sub
        '
        Public Function GetEnumerator() As System.Collections.IEnumerator _
                         Implements System.Collections.IEnumerable.GetEnumerator
            GetEnumerator = New BoundedStackEnumerator(_Items, Count)
        End Function
        '
        Private Class BoundedStackEnumerator : Implements IEnumerator
            Private _Items As Object()
            Private _Count As Integer
            Private _Cursor As Integer
            Public Sub New(ByVal items As Object(), ByVal count As Integer)
                _Items = items
                _Count = count
                _Cursor = _Count
            End Sub
            Public Function MoveNext() As Boolean _
                              Implements System.Collections.IEnumerator.MoveNext
                _Cursor -= 1
                MoveNext = (_Cursor >= 0)
            End Function
            Public ReadOnly Property Current() As Object _
                               Implements System.Collections.IEnumerator.Current
                Get
                    Current = _Items(_Cursor)
                End Get
            End Property
            Public Sub Reset() Implements System.Collections.IEnumerator.Reset
                _Cursor = _Count
            End Sub
        End Class
    End Class
    Class Client
        Public Shared Sub Main()
            Dim boundedStack1 As BoundedStack
            Dim enumerator As IEnumerator
            '
            boundedStack1 = New BoundedStack(3)
            enumerator = boundedStack1.GetEnumerator()
            Console.WriteLine(boundedStack1.Count = 0)
            Console.WriteLine(boundedStack1.Capacity = 3)
            Console.WriteLine(boundedStack1.Peek() Is Nothing)
            Console.WriteLine(enumerator.MoveNext() = False)
            Console.WriteLine()
            '
            boundedStack1.Pop()
            enumerator = boundedStack1.GetEnumerator()
            Console.WriteLine(boundedStack1.Count = 0)
            Console.WriteLine(boundedStack1.Capacity = 3)
            Console.WriteLine(boundedStack1.Peek() Is Nothing)
            Console.WriteLine(enumerator.MoveNext() = False)
            Console.WriteLine()
            '
            boundedStack1.Push(10)
            enumerator = boundedStack1.GetEnumerator()
            Console.WriteLine(boundedStack1.Count = 1)
            Console.WriteLine(boundedStack1.Capacity = 3)
            Console.WriteLine(Convert.ToInt32(boundedStack1.Peek()) = 10)
            Console.WriteLine(enumerator.MoveNext() = True)
            Console.WriteLine(Convert.ToInt32(enumerator.Current) = 10)
            Console.WriteLine(enumerator.MoveNext() = False)
            Console.WriteLine()
            '
            boundedStack1.Push(20)
            boundedStack1.Push(30)
            boundedStack1.Push(40)
            enumerator = boundedStack1.GetEnumerator()
            Console.WriteLine(boundedStack1.Count = 3)
            Console.WriteLine(boundedStack1.Capacity = 3)
            Console.WriteLine(Convert.ToInt32(boundedStack1.Peek()) = 30)
            Console.WriteLine(enumerator.MoveNext() = True)
            Console.WriteLine(Convert.ToInt32(enumerator.Current) = 30)
            Console.WriteLine(enumerator.MoveNext() = True)
            Console.WriteLine(Convert.ToInt32(enumerator.Current) = 20)
            Console.WriteLine(enumerator.MoveNext() = True)
            Console.WriteLine(Convert.ToInt32(enumerator.Current) = 10)
            Console.WriteLine(enumerator.MoveNext() = False)
            Console.WriteLine()
            '
            boundedStack1.Pop()
            enumerator = boundedStack1.GetEnumerator()
            Console.WriteLine(boundedStack1.Count = 2)
            Console.WriteLine(boundedStack1.Capacity = 3)
            Console.WriteLine(Convert.ToInt32(boundedStack1.Peek()) = 20)
            Console.WriteLine(enumerator.MoveNext() = True)
            Console.WriteLine(Convert.ToInt32(enumerator.Current) = 20)
            Console.WriteLine(enumerator.MoveNext() = True)
            Console.WriteLine(Convert.ToInt32(enumerator.Current) = 10)
            Console.WriteLine(enumerator.MoveNext() = False)
            Console.WriteLine()
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
True
True
True
True

True
True
True
True

True
True
True
True
True
True

True
True
True
True
True
True
True
True
True
True

True
True
True
True
True
True
True
True
Bij wijze van "defensief programmeren" gaan de Peek en Pop implementaties eerst na of we wel over voldoende elementen beschikken ( If Count > 0).

22.1.6. Iterator Pattern - Robuuste Iterator

Onderstaand voorbeeld bevat een BoundedQueue klasse, waarbij het aantal elementen begrenst is tot een bepaalde capaciteit.
Visual Basic 2012 Broncode - Codevoorbeeld 511
Namespace Example8
    Class BoundedQueue : Implements IEnumerable
        Protected _Items As Object()
        Protected _Count As Integer
        Public Sub New(ByVal capacity As Integer)
            ReDim _Items(capacity - 1)
        End Sub
        Public ReadOnly Property Count() As Integer
            Get
                Count = _Count
            End Get
        End Property
        Public ReadOnly Property Capacity() As Integer
            Get
                Capacity = _Items.Length
            End Get
        End Property
        Public Sub Enqueue(ByVal value As Object)
            If Count < Capacity Then
                _Items(Count) = value
                _Count += 1
            End If
        End Sub
        Public Function Peek() As Object
            Peek = _Items(0)
        End Function
        Public Sub Dequeue()
            For index As Integer = 0 To Count - 2
                _Items(index) = _Items(index + 1)
            Next
            _Items(Count - 1) = 0
            _Count -= 1
        End Sub
        '
        Public Overridable Function GetEnumerator() As IEnumerator _
                                            Implements IEnumerable.GetEnumerator
            GetEnumerator = New BoundedQueueEnumerator(_Items, Count)     ' (1)
        End Function
        '
        Class BoundedQueueEnumerator : Implements IEnumerator
            Private _Items As Object()
            Private _Count As Integer                                     ' (1)
            Private _Cursor As Integer = -1
            Public Sub New(ByVal items As Object(), ByVal count As Integer)
                _Items = items
                _Count = count
            End Sub
            Public Function MoveNext() As Boolean _
                                                 Implements IEnumerator.MoveNext
                _Cursor += 1
                MoveNext = (_Cursor < _Count)
            End Function
            Public ReadOnly Property Current() As Object _
                                                  Implements IEnumerator.Current
                Get
                    Current = _Items(_Cursor)
                End Get
            End Property
            Public Sub Reset() Implements IEnumerator.Reset
                _Cursor = -1
            End Sub
        End Class
    End Class
    Class Client1
        Public Shared BoundedQueue1 As BoundedQueue
        Public Shared Sub Main()
            BoundedQueue1 = New BoundedQueue(2)                          ' (2)
            PrintBoundedQueue1()
            '
            BoundedQueue1.Enqueue(1)
            BoundedQueue1.Enqueue(2)
            BoundedQueue1.Enqueue(3)                                     ' (2)
            PrintBoundedQueue1()
            '
            Console.ReadLine()
        End Sub
        Public Shared Sub PrintBoundedQueue1()
            Console.Write("boundedQueue1 : ")
            For Each element As Object In BoundedQueue1
                Console.Write(element.ToString() & " ")
            Next
            Console.WriteLine()
        End Sub
    End Class
End Namespace
Console Application Output
boundedQueue1 :
boundedQueue1 : 1 2
Het toevoegen van een derde element zal - gezien de queue "bounded" is - bij capaciteit twee genegeerd worden (2).

Bemerk hoe de enumerator hier nu ook de informatie van het aantal elementen nodig heeft (1) om te vermijden dat men over alle elementen van de interne Object() gaat enumereren.
Dit ook meteen de reden waarom we hier zelf een enumerator moeten definiëren, we kunnen immers in de IEnumerable.GetEnumerator implementatie niet zomaar de enumerator van de interne Object() (_Items.GetEnumerator()) opleveren.  Deze zou toelaten over alle elementen van deze array te enumereren.

Het "iteratorpattern" (pattern dat definieert hoe enumerators moeten functioneren) stelt dat een robuuste iterator (enumerator) een "snapshot" zou moeten zijn van de collectie.  Als een collectie wordt gewijzigd (bijvoorbeeld een element toegevoegd of verwijderd) terwijl reeds een enumerator werd opgevraagd, dan zou dit geen effect mogen hebben op deze enumerator.

Toch zou dit hier het geval zijn, in onderstaand voorbeeld wordt dit geïllustreerd :
Visual Basic 2012 Broncode - Codevoorbeeld 512
Namespace Example8
    Class Client2
        Public Shared Sub Main()
            Client1.BoundedQueue1 = New BoundedQueue(5)
            Client1.BoundedQueue1.Enqueue(1)
            Client1.BoundedQueue1.Enqueue(2)
            Client1.BoundedQueue1.Enqueue(3)
            '
            Client1.PrintBoundedQueue1()
            '
            Console.Write("boundedQueue1 : ")
            Dim elementCounter As Integer
            For Each element As Object In Client1.BoundedQueue1            ' (1)
                elementCounter += 1
                Console.Write(element.ToString() & " ")
                If elementCounter = 2 Then
                    Client1.BoundedQueue1.Dequeue()                        ' (2)
                End If
            Next
            Console.WriteLine()
            '
            Client1.PrintBoundedQueue1()
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
boundedQueue1 : 1 2 3
boundedQueue1 : 1 2 0
boundedQueue1 : 2 3
Bij het verwijderen van een element (2) tijdens het enumereren over de elementen van deze collectie (1) levert de enumerator het foutieve resultaat ( "1 2 0") op.
Volgens het iteratorpattern zou deze deze enumerator moeten werken met een snapshot van de toestand van de collectie op het moment dat deze enumerator wordt opgevraagd.  Het resultaat had hier "1 2 3" moeten zijn.

Het wijzigen van de originele collectie heeft effect op de enumerator die reeds voor de wijziging werd opgevraagd.  De enumerator werkt immers met dezelfde interne array instantie als de collectie zelf.

Dit probleem valt eenvoudig op te lossen door de enumerator met een kopie van de interne array van de collectie te laten werken :
Visual Basic 2012 Broncode - Codevoorbeeld 513
Namespace Example8
    Class BetterBoundedQueue : Inherits BoundedQueue
        Public Sub New(ByVal capacity As Integer)
            MyBase.New(capacity)
        End Sub
        Public Overrides Function GetEnumerator() As IEnumerator
            Dim itemsClone As Object() = DirectCast(_Items.Clone(), Object())
            GetEnumerator = New BoundedQueueEnumerator(itemsClone, Count)
        End Function
    End Class
    Class Client3
        Public Shared Sub Main()
            Client1.BoundedQueue1 = New BetterBoundedQueue(5)
            Client1.BoundedQueue1.Enqueue(1)
            Client1.BoundedQueue1.Enqueue(2)
            Client1.BoundedQueue1.Enqueue(3)
            '
            Client1.PrintBoundedQueue1()
            '
            Console.Write("boundedQueue1 : ")
            Dim elementCounter As Integer
            For Each element As Object In Client1.BoundedQueue1
                elementCounter += 1
                Console.Write(element.ToString() & " ")
                If elementCounter = 2 Then
                    Client1.BoundedQueue1.Dequeue()
                End If
            Next
            Console.WriteLine()
            '
            Client1.PrintBoundedQueue1()
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
boundedQueue1 : 1 2 3
boundedQueue1 : 1 2 3
boundedQueue1 : 2 3
Hier krijgen we wel het gewenste resultaat.

Het creëren van kopies van de interne collectie, om de enumerator van deze kopie gebruik te laten maken, heeft natuurlijk een bepaalde performance overhead, zeker indien de collectie veel elementen bevat.  Dit wordt dan ook vaak achterwege gelaten, waardoor men een niet robuuste iterator bekomt.

Een meer geraffineerde techniek (dan het klonen van de interne collectie) om een robuuste iterator te bekomen, zou eruit bestaan een registratiemechanisme op te bouwen, waarbij de enumerator verwittigd wordt bij het wijzigen van de collectie.

Een eenvoudige implementatie van dergelijk mechanisme wordt hieronder geïllustreerd :
Visual Basic 2012 Broncode - Codevoorbeeld 514
Namespace Example9
    Class BoundedQueue : Implements IEnumerable
        Public Event Changed()
        '
        Protected _Items As Object()
        Protected _Count As Integer
        Public Sub New(ByVal capacity As Integer)
            ReDim _Items(capacity - 1)
        End Sub
        Public ReadOnly Property Count() As Integer
            Get
                Count = _Count
            End Get
        End Property
        Public ReadOnly Property Capacity() As Integer
            Get
                Capacity = _Items.Length
            End Get
        End Property
        Public Sub Enqueue(ByVal value As Object)
            If Count < Capacity Then
                _Items(Count) = value
                RaiseEvent Changed()
                _Count += 1
            End If
        End Sub
        Public Function Peek() As Object
            Peek = _Items(0)
        End Function
        Public Sub Dequeue()
            For index As Integer = 0 To Count - 2
                _Items(index) = _Items(index + 1)
            Next
            _Items(Count - 1) = 0
            RaiseEvent Changed()
            _Count -= 1
        End Sub
        '
        Public Overridable Function GetEnumerator() As IEnumerator _
                                            Implements IEnumerable.GetEnumerator
            GetEnumerator = New BoundedQueueEnumerator(_Items, Me)
        End Function
        '
        Class BoundedQueueEnumerator : Implements IEnumerator
            Private _ItemsChanged As Boolean
            Private Sub onItemsChanged() Handles _BoundedQueue.Changed
                _ItemsChanged = True
            End Sub
            '
            Private WithEvents _BoundedQueue As BoundedQueue
            Private _Items As Object()
            Private _Cursor As Integer = -1
            Public Sub New(ByVal items As Object(), _
                           ByVal collection As BoundedQueue)
                _Items = items
                _BoundedQueue = collection
            End Sub
            Public Function MoveNext() As Boolean _
                                                 Implements IEnumerator.MoveNext
                If _ItemsChanged Then
                    Throw New InvalidOperationException( _
                                       "Collection was modified; " & _
                                       "enumeration operation may not execute.")
                Else
                    _Cursor += 1
                    MoveNext = (_Cursor < _BoundedQueue.Count)
                End If
            End Function
            Public ReadOnly Property Current() As Object _
                                                  Implements IEnumerator.Current
                Get
                    If _ItemsChanged Then
                        Throw New InvalidOperationException( _
                                       "Collection was modified; " & _
                                       "enumeration operation may not execute.")
                    Else
                        Current = _Items(_Cursor)
                    End If
                End Get
            End Property
            Public Sub Reset() Implements IEnumerator.Reset
                If _ItemsChanged Then
                    Throw New InvalidOperationException( _
                                       "Collection was modified; " & _
                                       "enumeration operation may not execute.")
                Else
                    _Cursor = -1
                End If
            End Sub
        End Class
    End Class
    Class Client1
        Private Shared _BoundedQueuue1 As BoundedQueue
        Public Shared Sub Main()
            _BoundedQueuue1 = New BoundedQueue(5)
            _BoundedQueuue1.Enqueue(1)
            _BoundedQueuue1.Enqueue(2)
            _BoundedQueuue1.Enqueue(3)
            '
            PrintBoundedQueue1()
            '
            Console.Write("boundedQueue1 : ")
            Dim elementCounter As Integer
            For Each element As Object In _BoundedQueuue1
                elementCounter += 1
                Console.Write(element.ToString() & " ")
                If elementCounter = 2 Then
                    _BoundedQueuue1.Dequeue()
                End If
            Next                                                           ' (1)
        End Sub
        Public Shared Sub PrintBoundedQueue1()
            Console.Write("boundedQueue1 : ")
            For Each element As Object In _BoundedQueuue1
                Console.Write(element.ToString() & " ")
            Next
            Console.WriteLine()
        End Sub
    End Class
End Namespace
Het registratiemechanisme is hier geïmplementeerd aan de hand van events, voor meer informatie betreffende events zie hiervoor het desbetreffende topic.

Er wordt naast de interne Object() ook een verwijzing naar de collectie zelf doorgegeven aan de enumerator.  Bij het wijzigen van de collectie zal deze een Changed event afvuren, waarbij de enumerator verwittigd wordt van deze wijziging.
Indien men vervolgens nog verder probeert te enumereren over deze enumerator zal deze enumerator een exception opwerpen (1).

Het optreden van een exceptie is in dergelijke situatie is aanvaardbaar, het is immers geen goede werkwijze om de collectie aan te passen terwijl men via een enumerator over de elementen enumereert.

Dit is ook wat er zou gebeuren bij het wijzigen van collecties van de meeste voorgedefinieerde collectie terwijl men aan het enumereren is over de enumerator :
Visual Basic 2012 Broncode - Codevoorbeeld 515
Namespace Example9
    Class Client2
        Public Shared Sub Main()
            Dim arrayList1 As New ArrayList
            arrayList1.Add(1)
            arrayList1.Add(2)
            arrayList1.Add(3)
            '
            Dim elementCounter As Integer
            For Each element As Object In arrayList1
                elementCounter += 1
                Console.Write(element.ToString() & " ")
                If elementCounter = 2 Then
                    arrayList1.Add(4)
                End If
            Next
        End Sub
    End Class
End Namespace
Ook hier treden dus dergelijke excepties op.

Dit artikel is gepubliceerd op maandag 15 oktober 2012 op vbvoorbeelden, bezoek de website voor een recente versie van dit artikel of andere artikels.