Delen via


Iterators (Visual Basic)

Een iterator kan worden gebruikt om verzamelingen zoals lijsten en matrices te doorlopen.

Een iteratormethode of get accessor voert een aangepaste iteratie uit op een verzameling. Een iterator-methode gebruikt de Yield-instructie om elk element één voor één te retourneren. Wanneer een Yield instructie is bereikt, wordt de huidige locatie in code onthouden. De uitvoering wordt opnieuw gestart vanaf die locatie wanneer de iterator-functie de volgende keer wordt aangeroepen.

U gebruikt een iterator van clientcode met behulp van een For Each... Volgende instructie of met behulp van een LINQ-query.

In het volgende voorbeeld zorgt de eerste iteratie van de For Each lus ervoor dat de uitvoering wordt voortgezet in de SomeNumbers iterator-methode totdat de eerste Yield instructie is bereikt. Deze iteratie retourneert een waarde van 3 en de huidige locatie in de iterator-methode wordt bewaard. Bij de volgende iteratie van de lus gaat de uitvoering in de iterator-methode verder vanaf waar deze is gebleven, en stopt opnieuw wanneer er een Yield instructie wordt bereikt. Deze iteratie retourneert een waarde van 5 en de huidige locatie in de iterator-methode wordt opnieuw bewaard. De lus wordt voltooid wanneer het einde van de iteratormethode is bereikt.

Sub Main()
    For Each number As Integer In SomeNumbers()
        Console.Write(number & " ")
    Next
    ' Output: 3 5 8
    Console.ReadKey()
End Sub

Private Iterator Function SomeNumbers() As System.Collections.IEnumerable
    Yield 3
    Yield 5
    Yield 8
End Function

Het retourtype van een iteratormethode of get accessor kan IEnumerablezijn, IEnumerable<T>of IEnumeratorIEnumerator<T>.

U kunt een Exit Function of-instructie Return gebruiken om de iteratie te beëindigen.

Een Visual Basic-iterator-functie of get -declaratie bevat een Iterator-wijzigingsfunctie .

Iterators zijn geïntroduceerd in Visual Basic in Visual Studio 2012.

Notitie

Voor alle voorbeelden in het artikel, met uitzondering van het eenvoudige iterator-voorbeeld, bevat u importinstructies voor de System.Collections en System.Collections.Generic naamruimten.

Eenvoudige Iterator

Het volgende voorbeeld heeft één Yield instructie die zich in een For bevindt... Volgende lus. In Main, elke iteratie van de For Each instructietekst maakt een aanroep naar de iterator-functie, die naar de volgende Yield instructie gaat.

Sub Main()
    For Each number As Integer In EvenSequence(5, 18)
        Console.Write(number & " ")
    Next
    ' Output: 6 8 10 12 14 16 18
    Console.ReadKey()
End Sub

Private Iterator Function EvenSequence(
ByVal firstNumber As Integer, ByVal lastNumber As Integer) _
As System.Collections.Generic.IEnumerable(Of Integer)

    ' Yield even numbers in the range.
    For number As Integer = firstNumber To lastNumber
        If number Mod 2 = 0 Then
            Yield number
        End If
    Next
End Function

Een verzamelingsklasse maken

In het volgende voorbeeld implementeert de DaysOfTheWeek klasse de IEnumerable interface, waarvoor een GetEnumerator methode is vereist. De compiler roept impliciet de GetEnumerator methode aan, die een IEnumerator.

De GetEnumerator methode retourneert elke tekenreeks één voor één met behulp van de Yield instructie en een Iterator wijzigingsfunctie bevindt zich in de functiedeclaratie.

Sub Main()
    Dim days As New DaysOfTheWeek()
    For Each day As String In days
        Console.Write(day & " ")
    Next
    ' Output: Sun Mon Tue Wed Thu Fri Sat
    Console.ReadKey()
End Sub

Private Class DaysOfTheWeek
    Implements IEnumerable

    Public days =
        New String() {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}

    Public Iterator Function GetEnumerator() As IEnumerator _
        Implements IEnumerable.GetEnumerator

        ' Yield each day of the week.
        For i As Integer = 0 To days.Length - 1
            Yield days(i)
        Next
    End Function
End Class

In het volgende voorbeeld wordt een Zoo klasse gemaakt die een verzameling dieren bevat.

De For Each instructie die verwijst naar het klasse-exemplaar (theZoo) roept de GetEnumerator methode impliciet aan. De For Each instructies die naar de Birds en Mammals eigenschappen verwijzen, gebruiken de AnimalsForType benoemde iterator-methode.

Sub Main()
    Dim theZoo As New Zoo()

    theZoo.AddMammal("Whale")
    theZoo.AddMammal("Rhinoceros")
    theZoo.AddBird("Penguin")
    theZoo.AddBird("Warbler")

    For Each name As String In theZoo
        Console.Write(name & " ")
    Next
    Console.WriteLine()
    ' Output: Whale Rhinoceros Penguin Warbler

    For Each name As String In theZoo.Birds
        Console.Write(name & " ")
    Next
    Console.WriteLine()
    ' Output: Penguin Warbler

    For Each name As String In theZoo.Mammals
        Console.Write(name & " ")
    Next
    Console.WriteLine()
    ' Output: Whale Rhinoceros

    Console.ReadKey()
End Sub

Public Class Zoo
    Implements IEnumerable

    ' Private members.
    Private animals As New List(Of Animal)

    ' Public methods.
    Public Sub AddMammal(ByVal name As String)
        animals.Add(New Animal With {.Name = name, .Type = Animal.TypeEnum.Mammal})
    End Sub

    Public Sub AddBird(ByVal name As String)
        animals.Add(New Animal With {.Name = name, .Type = Animal.TypeEnum.Bird})
    End Sub

    Public Iterator Function GetEnumerator() As IEnumerator _
        Implements IEnumerable.GetEnumerator

        For Each theAnimal As Animal In animals
            Yield theAnimal.Name
        Next
    End Function

    ' Public members.
    Public ReadOnly Property Mammals As IEnumerable
        Get
            Return AnimalsForType(Animal.TypeEnum.Mammal)
        End Get
    End Property

    Public ReadOnly Property Birds As IEnumerable
        Get
            Return AnimalsForType(Animal.TypeEnum.Bird)
        End Get
    End Property

    ' Private methods.
    Private Iterator Function AnimalsForType( _
    ByVal type As Animal.TypeEnum) As IEnumerable
        For Each theAnimal As Animal In animals
            If (theAnimal.Type = type) Then
                Yield theAnimal.Name
            End If
        Next
    End Function

    ' Private class.
    Private Class Animal
        Public Enum TypeEnum
            Bird
            Mammal
        End Enum

        Public Property Name As String
        Public Property Type As TypeEnum
    End Class
End Class

Blokken proberen

Visual Basic staat een Yield instructie toe in het Try blok van een Try... Vangen... Tot slot verklaring. Een Try blok met een Yield instructie kan blokken bevatten Catch en kan een Finally blok hebben.

Het volgende voorbeeld bevat Try, Catchen Finally blokken in een iterator-functie. Het Finally blok in de iteratorfunctie wordt uitgevoerd voordat de For Each iteratie is voltooid.

Sub Main()
    For Each number As Integer In Test()
        Console.WriteLine(number)
    Next
    Console.WriteLine("For Each is done.")

    ' Output:
    '  3
    '  4
    '  Something happened. Yields are done.
    '  Finally is called.
    '  For Each is done.
    Console.ReadKey()
End Sub

Private Iterator Function Test() As IEnumerable(Of Integer)
    Try
        Yield 3
        Yield 4
        Throw New Exception("Something happened. Yields are done.")
        Yield 5
        Yield 6
    Catch ex As Exception
        Console.WriteLine(ex.Message)
    Finally
        Console.WriteLine("Finally is called.")
    End Try
End Function

Een Yield instructie kan niet binnen een Catch blok of blok Finally staan.

Als de For Each hoofdtekst (in plaats van de iteratormethode) een uitzondering genereert, wordt een Catch blok in de iterator-functie niet uitgevoerd, maar wordt er een Finally blok in de iterator-functie uitgevoerd. Een Catch blok in een iteratorfunctie onderschept alleen uitzonderingen die voorkomen in de iteratorfunctie.

Anonieme methoden

In Visual Basic kan een anonieme functie een iteratorfunctie zijn. In het volgende voorbeeld ziet u dit.

Dim iterateSequence = Iterator Function() _
                      As IEnumerable(Of Integer)
                          Yield 1
                          Yield 2
                      End Function

For Each number As Integer In iterateSequence()
    Console.Write(number & " ")
Next
' Output: 1 2
Console.ReadKey()

Het volgende voorbeeld heeft een niet-iteratormethode waarmee de argumenten worden gevalideerd. De methode retourneert het resultaat van een anonieme iterator die de verzamelingselementen beschrijft.

Sub Main()
    For Each number As Integer In GetSequence(5, 10)
        Console.Write(number & " ")
    Next
    ' Output: 5 6 7 8 9 10
    Console.ReadKey()
End Sub

Public Function GetSequence(ByVal low As Integer, ByVal high As Integer) _
As IEnumerable
    ' Validate the arguments.
    If low < 1 Then
        Throw New ArgumentException("low is too low")
    End If
    If high > 140 Then
        Throw New ArgumentException("high is too high")
    End If

    ' Return an anonymous iterator function.
    Dim iterateSequence = Iterator Function() As IEnumerable
                              For index = low To high
                                  Yield index
                              Next
                          End Function
    Return iterateSequence()
End Function

Als de validatie zich in plaats daarvan in de iterator-functie bevindt, kan de validatie pas worden uitgevoerd als de eerste iteratie van de For Each hoofdtekst is gestart.

Iterators gebruiken met een algemene lijst

In het volgende voorbeeld implementeert de Stack(Of T) algemene klasse de IEnumerable<T> algemene interface. De Push methode wijst waarden toe aan een matrix van het type T. De GetEnumerator methode retourneert de matrixwaarden met behulp van de Yield instructie.

Naast de algemene methode moet ook de niet-generieke GetEnumeratorGetEnumerator methode worden geïmplementeerd. Dit komt omdat IEnumerable<T> ze overnemen van IEnumerable. De niet-algemene implementatie wordt uitgesteld tot de algemene implementatie.

In het voorbeeld worden benoemde iterators gebruikt om verschillende manieren te ondersteunen om dezelfde verzameling gegevens te doorlopen. Deze benoemde iterators zijn de TopToBottom en BottomToTop eigenschappen en de TopN methode.

De BottomToTop eigenschapsdeclaratie bevat het Iterator trefwoord.

Sub Main()
    Dim theStack As New Stack(Of Integer)

    ' Add items to the stack.
    For number As Integer = 0 To 9
        theStack.Push(number)
    Next

    ' Retrieve items from the stack.
    ' For Each is allowed because theStack implements
    ' IEnumerable(Of Integer).
    For Each number As Integer In theStack
        Console.Write("{0} ", number)
    Next
    Console.WriteLine()
    ' Output: 9 8 7 6 5 4 3 2 1 0

    ' For Each is allowed, because theStack.TopToBottom
    ' returns IEnumerable(Of Integer).
    For Each number As Integer In theStack.TopToBottom
        Console.Write("{0} ", number)
    Next
    Console.WriteLine()
    ' Output: 9 8 7 6 5 4 3 2 1 0

    For Each number As Integer In theStack.BottomToTop
        Console.Write("{0} ", number)
    Next
    Console.WriteLine()
    ' Output: 0 1 2 3 4 5 6 7 8 9

    For Each number As Integer In theStack.TopN(7)
        Console.Write("{0} ", number)
    Next
    Console.WriteLine()
    ' Output: 9 8 7 6 5 4 3

    Console.ReadKey()
End Sub

Public Class Stack(Of T)
    Implements IEnumerable(Of T)

    Private values As T() = New T(99) {}
    Private top As Integer = 0

    Public Sub Push(ByVal t As T)
        values(top) = t
        top = top + 1
    End Sub

    Public Function Pop() As T
        top = top - 1
        Return values(top)
    End Function

    ' This function implements the GetEnumerator method. It allows
    ' an instance of the class to be used in a For Each statement.
    Public Iterator Function GetEnumerator() As IEnumerator(Of T) _
        Implements IEnumerable(Of T).GetEnumerator

        For index As Integer = top - 1 To 0 Step -1
            Yield values(index)
        Next
    End Function

    Public Iterator Function GetEnumerator1() As IEnumerator _
        Implements IEnumerable.GetEnumerator

        Yield GetEnumerator()
    End Function

    Public ReadOnly Property TopToBottom() As IEnumerable(Of T)
        Get
            Return Me
        End Get
    End Property

    Public ReadOnly Iterator Property BottomToTop As IEnumerable(Of T)
        Get
            For index As Integer = 0 To top - 1
                Yield values(index)
            Next
        End Get
    End Property

    Public Iterator Function TopN(ByVal itemsFromTop As Integer) _
        As IEnumerable(Of T)

        ' Return less than itemsFromTop if necessary.
        Dim startIndex As Integer =
            If(itemsFromTop >= top, 0, top - itemsFromTop)

        For index As Integer = top - 1 To startIndex Step -1
            Yield values(index)
        Next
    End Function
End Class

Syntaxisinformatie

Een iterator kan optreden als een methode of get accessor. Een iterator kan niet optreden in een gebeurtenis, instantieconstructor, statische constructor of statische destructor.

Er moet een impliciete conversie bestaan van het expressietype in de Yield instructie naar het retourtype van de iterator.

In Visual Basic kan een iteratormethode geen ByRef parameters hebben.

In Visual Basic is 'Opbrengst' geen gereserveerd woord en heeft alleen speciale betekenis wanneer deze wordt gebruikt in een Iterator methode of get accessor.

Technische implementatie

Hoewel u een iterator schrijft als een methode, vertaalt de compiler deze in een geneste klasse die in feite een statusmachine is. Deze klasse houdt de positie van de iterator bij zolang de For Each...Next lus in de clientcode wordt voortgezet.

Als u wilt zien wat de compiler doet, kunt u het hulpprogramma Ildasm.exe gebruiken om de algemene tussentaalcode weer te geven die wordt gegenereerd voor een iterator-methode.

Wanneer u een iterator voor een klasse of struct maakt, hoeft u de hele IEnumerator interface niet te implementeren. Wanneer de compiler de iterator detecteert, worden de Current, MoveNexten Dispose methoden van de IEnumerator of IEnumerator<T> interface automatisch gegenereerd.

Bij elke opeenvolgende iteratie van de For Each…Next lus (of de directe aanroep naar IEnumerator.MoveNext), wordt de volgende iterator-codetekst hervat na de vorige Yield instructie. Vervolgens gaat het verder met de volgende Yield instructie totdat het einde van de iteratortekst is bereikt, of totdat een Exit Function of Return instructie is aangetroffen.

Iterators bieden geen ondersteuning voor de IEnumerator.Reset methode. Als u opnieuw wilt herhalen vanaf het begin, moet u een nieuwe iterator verkrijgen.

Zie de Visual Basic Language-specificatie voor meer informatie.

Gebruik van Iterators

Met iterators kunt u de eenvoud van een For Each lus behouden wanneer u complexe code moet gebruiken om een lijstreeks te vullen. Dit kan handig zijn als u het volgende wilt doen:

  • Wijzig de lijstreeks na de eerste For Each lusiteratie.

  • Vermijd het volledig laden van een grote lijst vóór de eerste iteratie van een For Each lus. Een voorbeeld is een pagina ophalen om een batch tabelrijen te laden. Een ander voorbeeld is de EnumerateFiles methode, die iterators implementeert in .NET Framework.

  • De lijst inkapselen in de iterator. In de iterator-methode kunt u de lijst maken en vervolgens elk resultaat in een lus opleveren.

Zie ook