Итераторы (Visual Basic)
Итератор можно использовать для прохода по коллекции, такой как список или массив.
Метод итератора или метод доступа get
выполняет настраиваемую итерацию по коллекции. Метод итератора использует инструкцию Yield для возврата каждого элемента по одному за раз. При достижении инструкции Yield
текущее расположение в коде запоминается. При следующем вызове функции итератора выполнение возобновляется с этого места.
Вы используете итератор из клиентского кода с помощью элемента For Each... Следующая инструкция или с помощью запроса LINQ.
В приведенном ниже примере первая итерация цикла For Each
приводит к вызову метода итератора SomeNumbers
, пока не будет достигнут первый оператор Yield
. Эта итерация возвращает значение 3. Текущее расположение в методе итератора сохраняется. В следующей итерации цикла выполнение метода итератора возобновляется с того же места и снова приостанавливается при достижении оператора Yield
. Эта итерация возвращает значение 5. Текущее расположение в методе итератора снова сохраняется. Цикл завершается при достижении конца метода итератора.
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
Типом возвращаемого метода итератора или метода доступа get
может быть IEnumerable, IEnumerable<T>, IEnumerator или IEnumerator<T>.
Для завершения итерации можно использовать Exit Function
инструкцию или Return
инструкцию.
Функция итератора Visual Basic или get
объявление метода доступа включает модификатор итератора .
Итераторы появились в Visual Basic в Visual Studio 2012.
Примечание.
Для всех примеров в статье, кроме примера Simple Iterator, включите инструкции Import для System.Collections
пространств имен и System.Collections.Generic
пространств имен.
Простой итератор
В следующем примере есть одна Yield
инструкция, которая находится внутри for... Следующий цикл. В методе Main
каждая итерация оператора For Each
создает вызов функции итератора, которая выполняет следующий оператор Yield
.
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
Создание класса коллекции
В следующем примере класс DaysOfTheWeek
реализует интерфейс IEnumerable, которому требуется метод GetEnumerator. Компилятор неявно вызывает метод GetEnumerator
, который возвращает IEnumerator.
Метод GetEnumerator
возвращает каждую строку по одному за раз с помощью Yield
инструкции, а Iterator
модификатор находится в объявлении функции.
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
В приведенном ниже примере создается класс Zoo
, содержащий коллекцию животных.
Оператор For Each
, который обращается к экземпляру класса (theZoo
), неявно вызывает метод GetEnumerator
. Операторы For Each
, которые обращаются к свойствам Birds
и Mammals
, используют метод итератора с именем AnimalsForType
.
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
Пробные блоки
Visual Basic позволяет оператору Yield
Try
в блоке try... Поймать... Наконец, оператор. Блок Try
с оператором Yield
может содержать Catch
блоки и может иметь Finally
блоки.
В следующем примере содержатся Try
Catch
блоки и Finally
блоки в функции итератора. Блок Finally
в функции итератора выполняется до For Each
завершения итерации.
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
Оператор Yield
не может находиться внутри Catch
блока или Finally
блока.
For Each
Если тело (вместо метода итератора) создает исключение, Catch
блок в функции итератора не выполняется, но Finally
выполняется блок в функции итератора. Блок Catch
внутри функции итератора перехватывает только исключения, происходящие внутри функции итератора.
Анонимные методы
В Visual Basic анонимная функция может быть итератором. Это показано в следующем примере.
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()
В следующем примере есть метод, отличный от итератора, который проверяет аргументы. Метод возвращает результат анонимного итератора, описывающего элементы коллекции.
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
Если проверка находится вместо функции итератора, проверка не может выполняться до начала первой итерации For Each
текста.
Использование итераторов с универсальным списком
В следующем примере универсальный класс Stack(Of T)
также реализует универсальный интерфейс IEnumerable<T>. Метод Push
присваивает значения массиву типа T
. Метод GetEnumerator возвращает массив значений с помощью оператора Yield
.
Помимо универсального метода GetEnumerator должен быть реализован и неуниверсальный метод GetEnumerator. Это связано с тем, что IEnumerable<T> наследуется от IEnumerable. Неуниверсальная реализация подчиняется универсальной реализации.
В этом примере используются именованные итераторы для поддержки различных способов итерации по одной и той же коллекции данных. Эти именованные итераторы являются свойствами TopToBottom
и BottomToTop
и методом TopN
.
Объявление BottomToTop
свойства включает 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
Сведения о синтаксисе
Итератор может являться методом или методом доступа get
. Итератор не может использоваться в событии, конструкторе экземпляра, статическом конструкторе или статическом деструкторе.
Должно существовать неявное преобразование выражения типа в операторе Yield
в возвращаемый тип итератора.
В Visual Basic метод итератора не может иметь никаких ByRef
параметров.
В Visual Basic "Доходность" не является зарезервированным словом и имеет особое значение, только если оно используется в методе или get
методе Iterator
доступа.
Техническая реализация
Хотя итератор создается как метод, компилятор переводит его во вложенный класс, который фактически является конечным автоматом. Этот класс отслеживает положение итератора, пока в клиентском коде выполняется цикл For Each...Next
.
Чтобы узнать, что делает компилятор, можно использовать средство Ildasm.exe для просмотра кода общего промежуточного языка, созданного для метода итератора.
При создании итератора для класса или структуры не требуется реализовать весь IEnumerator интерфейс. Когда компилятор обнаруживает итератор, он автоматически создает методы Current
, MoveNext
и Dispose
интерфейса IEnumerator или IEnumerator<T>.
В каждой последовательной итерации цикла For Each…Next
(или непосредственном вызове метода IEnumerator.MoveNext
) код тела следующего итератора возобновляет выполнение после предыдущего оператора Yield
. Затем он продолжается до следующего Yield
оператора до конца текста итератора или до тех пор, пока Exit Function
не будет обнаружена или Return
инструкция.
Итераторы не поддерживают IEnumerator.Reset метод. Для повторной итерации сначала необходимо получить новый итератор.
Дополнительные сведения см. в спецификации языка Visual Basic.
Использование итераторов
Итераторы позволяют поддерживать простоту цикла For Each
, когда необходимо использовать сложный код для заполнения последовательности списков. Это может оказаться полезным в следующих случаях:
Изменение последовательности списков после первой итерации цикла
For Each
.Если необходимо избежать полной загрузки большого списка перед первой итерацией цикла
For Each
. Пример: при постраничной загрузке пакета строк таблицы. Другой пример — метод EnumerateFiles, реализующий итераторы в .NET Framework.Инкапсулирование построения списка в итераторе. В методе итератора можно построить список, а затем выдавать каждый результат в цикле.