Dela via


Iteratorer (Visual Basic)

En iterator kan användas för att gå igenom samlingar som listor och matriser.

En iteratormetod eller get -accessor utför en anpassad iteration över en samling. En iteratormetod använder yield-instruktionen för att returnera varje element ett i taget. När en Yield instruktion har nåtts sparas den aktuella platsen i koden. Körningen startas om från den platsen nästa gång iteratorfunktionen anropas.

Du använder en iterator från klientkoden med hjälp av en För varje... Nästa instruktion, eller med hjälp av en LINQ-fråga.

I följande exempel gör den första iterationen av loopen att körningen For EachSomeNumbers fortsätter i iteratormetoden tills den första Yield instruktionen har nåtts. Den här iterationen returnerar värdet 3 och den aktuella platsen i iteratormetoden behålls. Vid nästa iteration av loopen fortsätter körningen i iteratormetoden från där den slutade och stoppas igen när den når en Yield -instruktion. Den här iterationen returnerar värdet 5 och den aktuella platsen i iteratormetoden behålls igen. Loopen slutförs när iteratormetodens slut har nåtts.

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

Returtypen för en iteratormetod eller get -accessor kan vara IEnumerable, IEnumerable<T>, IEnumeratoreller IEnumerator<T>.

Du kan använda en Exit Function -instruktion för Return att avsluta iterationen.

En Visual Basic-iteratorfunktion eller get åtkomstdeklaration innehåller en Iterator-modifierare .

Iteratorer introducerades i Visual Basic i Visual Studio 2012.

Kommentar

För alla exempel i artikeln förutom Simple Iterator-exemplet inkluderar du importinstruktioner för System.Collections namnrymderna och System.Collections.Generic .

Enkel iterator

I följande exempel finns en enda Yield instruktion som finns i en For... Nästa loop. I Mainskapar varje iteration av instruktionstexten For Each ett anrop till iteratorfunktionen, som fortsätter till nästa Yield instruktion.

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

Skapa en samlingsklass

I följande exempel DaysOfTheWeek implementerar IEnumerable klassen gränssnittet, som kräver en GetEnumerator metod. Kompilatorn anropar GetEnumerator implicit metoden, som returnerar en IEnumerator.

Metoden GetEnumerator returnerar varje sträng en i taget med hjälp av -instruktionen Yield och en Iterator modifierare finns i funktionsdeklarationen.

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

I följande exempel skapas en Zoo klass som innehåller en samling djur.

Instruktionen For Each som refererar till klassinstansen (theZoo) anropar GetEnumerator implicit metoden. De For Each instruktioner som refererar till Birds egenskaperna och Mammals använder den namngivna AnimalsForType iteratormetoden.

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

Prova block

Visual Basic tillåter en Yield instruktion i Try blocket för ett försök... Fånga... Slutligen -instruktion. Ett Try block som har en Yield -instruktion kan ha Catch block och kan ha ett Finally block.

I följande exempel ingår Try, Catchoch Finally block i en iteratorfunktion. Blocket Finally i iteratorfunktionen körs innan iterationen For Each är klar.

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

En Yield instruktion får inte finnas i ett Catch block eller ett Finally block.

Om brödtexten For Each (i stället för iteratormetoden) utlöser ett undantag körs inte ett Catch block i iteratorfunktionen, men ett Finally block i iteratorfunktionen körs. Ett Catch block i en iteratorfunktion fångar bara upp undantag som inträffar i iteratorfunktionen.

Anonyma metoder

I Visual Basic kan en anonym funktion vara en iteratorfunktion. I följande exempel visas detta.

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()

I följande exempel finns en icke-iteratormetod som validerar argumenten. Metoden returnerar resultatet av en anonym iterator som beskriver samlingselementen.

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

Om valideringen i stället finns i iteratorfunktionen kan verifieringen inte utföras förrän den första iterationen av brödtexten har påbörjats For Each .

Använda iteratorer med en allmän lista

I följande exempel implementerar den Stack(Of T) generiska klassen det IEnumerable<T> generiska gränssnittet. Metoden Push tilldelar värden till en matris av typen T. Metoden GetEnumerator returnerar matrisvärdena med hjälp av -instruktionen Yield .

Förutom den generiska GetEnumerator metoden måste även den icke-generiska GetEnumerator metoden implementeras. Det beror på att IEnumerable<T> ärver från IEnumerable. Den icke-generiska implementeringen skjuter upp den allmänna implementeringen.

I exemplet används namngivna iteratorer för att stödja olika sätt att iterera genom samma datainsamling. Dessa namngivna iteratorer är TopToBottom egenskaperna och BottomToTop och TopN metoden.

Egenskapsdeklarationen BottomToTop innehåller nyckelordet Iterator .

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

Syntaxinformation

En iterator kan ske som en metod eller get accessor. En iterator kan inte inträffa i en händelse, instanskonstruktor, statisk konstruktor eller statisk destructor.

En implicit konvertering måste finnas från uttryckstypen i -instruktionen Yield till iteratorns returtyp.

I Visual Basic kan en iteratormetod inte ha några ByRef parametrar.

I Visual Basic är "Yield" inte ett reserverat ord och har särskild betydelse endast när det används i en Iterator metod eller get accessor.

Teknisk implementering

Även om du skriver en iterator som en metod översätter kompilatorn den till en kapslad klass som i själva verket är en tillståndsdator. Den här klassen håller reda på iteratorns position så länge loopen For Each...Next i klientkoden fortsätter.

Om du vill se vad kompilatorn gör kan du använda verktyget Ildasm.exe för att visa den gemensamma mellanliggande språkkoden som genereras för en iteratormetod.

När du skapar en iterator för en klass eller struct behöver du inte implementera hela IEnumerator gränssnittet. När kompilatorn identifierar iteratorn genereras Currentautomatiskt metoderna IEnumerator , MoveNextoch och Dispose i gränssnittet.IEnumerator<T>

På varje efterföljande iteration av loopen For Each…Next (eller direktanropet till IEnumerator.MoveNext) återupptas nästa iteratorkodtext efter föregående Yield instruktion. Den fortsätter sedan till nästa Yield -instruktion tills slutet av iteratortexten har nåtts, eller tills en instruktion eller Return -Exit Functioninstruktion påträffas.

Iteratorer stöder IEnumerator.Reset inte metoden. Om du vill iterera igen från början måste du skaffa en ny iterator.

Mer information finns i Visual Basic Language Specification.

Användning av iteratorer

Med iteratorer kan du upprätthålla enkelheten i en For Each loop när du behöver använda komplex kod för att fylla i en listsekvens. Detta kan vara användbart när du vill göra följande:

  • Ändra listsekvensen efter den första For Each loop-iterationen.

  • Undvik att helt läsa in en stor lista före den första iterationen av en For Each loop. Ett exempel är en sidhämtning för att läsa in en batch med tabellrader. Ett annat exempel är metoden EnumerateFiles som implementerar iteratorer i .NET Framework.

  • Kapsla in att skapa listan i iteratorn. I iteratormetoden kan du skapa listan och sedan ge varje resultat i en loop.

Se även