Iteratory (Visual Basic)
Iterator może służyć do przechodzenia przez kolekcje, takie jak listy i tablice.
Metoda iteratora lub get
metoda dostępu wykonuje iterację niestandardową w kolekcji. Metoda iteratora używa instrukcji Yield , aby zwrócić każdy element pojedynczo. Yield
Po osiągnięciu instrukcji zostanie zapamiętana bieżąca lokalizacja w kodzie. Wykonanie jest uruchamiane ponownie z tej lokalizacji przy następnym wywołaniu funkcji iteratora.
Iterator jest używany z kodu klienta przy użyciu elementu dla każdego... Następna instrukcja lub przy użyciu zapytania LINQ.
W poniższym przykładzie pierwsza iteracja For Each
pętli powoduje kontynuowanie wykonywania w SomeNumbers
metodzie iteratora do momentu osiągnięcia pierwszej Yield
instrukcji. Ta iteracja zwraca wartość 3, a bieżąca lokalizacja w metodzie iteratora jest zachowywana. W następnej iteracji pętli wykonywanie w metodzie iteratora jest kontynuowane od miejsca, w którym została przerwana, ponownie zatrzymując się, gdy osiągnie instrukcję Yield
. Ta iteracja zwraca wartość 5, a bieżąca lokalizacja w metodzie iteratora jest ponownie zachowywana. Pętla kończy się po osiągnięciu końca metody iteratora.
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
Zwracany typ metody iteratora lub get
metody dostępu może być IEnumerable, IEnumerable<T>, IEnumeratorlub IEnumerator<T>.
Możesz użyć instrukcji Exit Function
or Return
, aby zakończyć iterację.
Funkcja iteratora języka Visual Basic lub get
deklaracja metody dostępu zawiera modyfikator iteratora .
Iteratory zostały wprowadzone w Visual Basic w programie Visual Studio 2012.
Uwaga
Dla wszystkich przykładów w artykule z wyjątkiem prostego przykładu iteratora dołącz instrukcje Import dla System.Collections
przestrzeni nazw i System.Collections.Generic
.
Prosty iterator
Poniższy przykład zawiera pojedynczą Yield
instrukcję, która znajduje się wewnątrz elementu For... Następna pętla. W Main
pliku każda iteracja For Each
treści instrukcji tworzy wywołanie funkcji iteratora, która przechodzi do następnej Yield
instrukcji.
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
Tworzenie klasy kolekcji
W poniższym przykładzie DaysOfTheWeek
klasa implementuje IEnumerable interfejs, który wymaga GetEnumerator metody. Kompilator niejawnie wywołuje metodę GetEnumerator
, która zwraca IEnumeratorelement .
Metoda GetEnumerator
zwraca każdy ciąg pojedynczo przy użyciu instrukcji Yield
, a Iterator
modyfikator znajduje się w deklaracji funkcji.
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
Poniższy przykład tworzy klasę Zoo
zawierającą kolekcję zwierząt.
Instrukcja For Each
, która odwołuje się do wystąpienia klasy (theZoo
) niejawnie wywołuje metodę GetEnumerator
. Instrukcje For Each
odwołujące się do Birds
właściwości i Mammals
używają metody nazwanej AnimalsForType
iteratora.
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
Bloki try
Visual Basic zezwala na Try
instrukcję Yield
w bloku Try... Złapać... Finally, instrukcja. Blok Try
zawierający instrukcję Yield
może zawierać Catch
bloki i może mieć Finally
blok.
Poniższy przykład obejmuje Try
bloki , Catch
i Finally
w funkcji iteratora. Blok Finally
w funkcji iteratora jest wykonywany przed zakończeniem For Each
iteracji.
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
Instrukcja Yield
nie może znajdować się wewnątrz Catch
bloku lub Finally
bloku.
For Each
Jeśli treść (zamiast metody iteratora) zgłasza wyjątek, Catch
blok w funkcji iteratora nie jest wykonywany, ale Finally
jest wykonywany blok w funkcji iteratora. Blok Catch
wewnątrz funkcji iteratora przechwytuje tylko wyjątki występujące wewnątrz funkcji iteratora.
Metody anonimowe
W języku Visual Basic funkcja anonimowa może być funkcją iteratora. Ilustruje to poniższy przykład.
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()
Poniższy przykład zawiera metodę inną niż iterator, która weryfikuje argumenty. Metoda zwraca wynik anonimowego iteratora opisującego elementy kolekcji.
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
Jeśli walidacja znajduje się zamiast tego wewnątrz funkcji iteratora, nie można wykonać walidacji do momentu rozpoczęcia pierwszej iteracji For Each
treści.
Używanie iteratorów z listą ogólną
W poniższym przykładzie Stack(Of T)
klasa ogólna implementuje IEnumerable<T> interfejs ogólny. Metoda Push
przypisuje wartości do tablicy typu T
. Metoda GetEnumerator zwraca wartości tablicy przy użyciu instrukcji Yield
.
Oprócz metody ogólnej GetEnumerator należy również zaimplementować metodę niegeneryjną GetEnumerator . Dzieje się tak, ponieważ IEnumerable<T> dziedziczy z elementu IEnumerable. Implementacja niegeneryczna odchyli się od implementacji ogólnej.
W przykładzie użyto iteratorów nazwanych do obsługi różnych sposobów iteracji za pośrednictwem tej samej kolekcji danych. Te nazwane iteratory są TopToBottom
właściwościami i BottomToTop
oraz TopN
metodą .
Deklaracja BottomToTop
właściwości zawiera Iterator
słowo kluczowe .
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
Informacje o składni
Iterator może wystąpić jako metoda lub get
metoda dostępu. Iterator nie może wystąpić w przypadku zdarzenia, konstruktora wystąpienia, konstruktora statycznego lub destruktora statycznego.
Niejawna konwersja musi istnieć z typu wyrażenia w instrukcji Yield
do zwracanego typu iteratora.
W języku Visual Basic metoda iteratora nie może mieć żadnych ByRef
parametrów.
W języku Visual Basic "Yield" nie jest słowem zarezerwowanym i ma specjalne znaczenie tylko wtedy, gdy jest używany w Iterator
metodzie lub get
metodzie dostępu.
Realizacja techniczna
Mimo że iterator jest zapisywany jako metoda, kompilator tłumaczy go na klasę zagnieżdżoną, która jest w efekcie maszyną stanu. Ta klasa śledzi położenie iteratora, dopóki pętla For Each...Next
w kodzie klienta będzie kontynuowana.
Aby zobaczyć, co robi kompilator, możesz użyć narzędzia Ildasm.exe, aby wyświetlić wspólny kod języka pośredniego wygenerowany dla metody iteratora.
Podczas tworzenia iteratora dla klasy lub struktury nie trzeba implementować całego IEnumerator interfejsu. Gdy kompilator wykryje iterator, automatycznie generuje Current
metody , MoveNext
i Dispose
interfejsu IEnumerator .IEnumerator<T>
W każdej kolejnej For Each…Next
iteracji pętli (lub wywołania bezpośredniego do IEnumerator.MoveNext
), następna treść kodu iteratora zostanie wznowione po poprzedniej Yield
instrukcji. Następnie kontynuuje kolejną Yield
instrukcję do końca treści iteratora lub do momentu Exit Function
napotkania instrukcji or Return
.
Iteratory nie obsługują IEnumerator.Reset metody . Aby ponownie wykonać iterację od początku, należy uzyskać nowy iterator.
Aby uzyskać dodatkowe informacje, zobacz Specyfikację języka Visual Basic.
Korzystanie z iteratorów
Iteratory umożliwiają utrzymanie prostoty pętli, gdy trzeba użyć złożonego For Each
kodu do wypełnienia sekwencji listy. Może to być przydatne, jeśli chcesz wykonać następujące czynności:
Zmodyfikuj sekwencję listy po iteracji pierwszej
For Each
pętli.Unikaj pełnego ładowania dużej listy przed pierwszą
For Each
iterację pętli. Przykładem jest pobieranie stronicowane w celu załadowania partii wierszy tabeli. Innym przykładem jest EnumerateFiles metoda, która implementuje iteratory w programie .NET Framework.Hermetyzowanie tworzenia listy w iteratorze. W metodzie iteratora można utworzyć listę, a następnie uzyskać każdy wynik w pętli.