Анонимные методы в Visual Basic
В Visual Basic (но не в C#) анонимная функция может быть функцией итератора. Это показано в приведенном ниже примере.
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()
В следующем примере Visual Basic содержится метод не итератора, который проверяет аргументы. Метод возвращает результат анонимного итератора, который описывает элементы коллекции.
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. Метод Push присваивает значения массиву типа T. Метод GetEnumerator возвращает значения массива с помощью оператора Yield или yield return.
В дополнение к универсальному методу GetEnumerator, не универсальный метод GetEnumerator также должен быть реализован.Это происходит потому, что IEnumerable наследуется от IEnumerable. Не универсальная реализация подчиняется универсальной реализации.
В этом примере используются именованные итераторы для поддержки различных способов итерации по одной и той же коллекции данных. Эти именованные итераторы являются свойствами TopToBottom и BottomToTop, и методом TopN.
Свойство BottomToTop использует итератор в методе доступа get. В коде Visual Basic объявление свойства содержит ключевое слово Iterator.
static void Main() { Stack<int> theStack = new Stack<int>(); // Add items to the stack. for (int number = 0; number <= 9; number++) { theStack.Push(number); } // Retrieve items from the stack. // foreach is allowed because theStack implements // IEnumerable<int>. foreach (int number in theStack) { Console.Write("{0} ", number); } Console.WriteLine(); // Output: 9 8 7 6 5 4 3 2 1 0 // foreach is allowed, because theStack.TopToBottom // returns IEnumerable(Of Integer). foreach (int number in theStack.TopToBottom) { Console.Write("{0} ", number); } Console.WriteLine(); // Output: 9 8 7 6 5 4 3 2 1 0 foreach (int number in theStack.BottomToTop) { Console.Write("{0} ", number); } Console.WriteLine(); // Output: 0 1 2 3 4 5 6 7 8 9 foreach (int number in theStack.TopN(7)) { Console.Write("{0} ", number); } Console.WriteLine(); // Output: 9 8 7 6 5 4 3 Console.ReadKey(); } public class Stack<T> : IEnumerable<T> { private T[] values = new T[100]; private int top = 0; public void Push(T t) { values[top] = t; top++; } public T Pop() { top--; return values[top]; } // This method implements the GetEnumerator method. It allows // an instance of the class to be used in a foreach statement. public IEnumerator<T> GetEnumerator() { for (int index = top - 1; index >= 0; index--) { yield return values[index]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerable<T> TopToBottom { get { return this; } } public IEnumerable<T> BottomToTop { get { for (int index = 0; index <= top - 1; index++) { yield return values[index]; } } } public IEnumerable<T> TopN(int itemsFromTop) { // Return less than itemsFromTop if necessary. int startIndex = itemsFromTop >= top ? 0 : top - itemsFromTop; for (int index = top - 1; index >= startIndex; index--) { yield return values[index]; } } }
Сведения о синтаксисе
Итератор может происходить как метод или метод доступа get. Итератор не может произойти в событии, конструкторе экземпляра, статическом конструкторе или статическом деструкторе.
Должно существовать неявное преобразование из выражения типа в операторе C# Yield (Visual Basic) или yield return к возвращаемому типу итератора.
В Visual Basic методы с модификатором Iterator не могут иметь параметры ByRef. В C# методы с модификатором Iterator не могут иметь параметры ref или out.
В Visual Basic "Yield" не является зарезервированным словом и имеет специальное значение, только если он используется в методе Iterator или методе доступа get. В C#, "yield" не является зарезервированным словом и имеет специальное значение, только если он используется перед ключевым словом return или break.
Техническая реализация
Хотя итератор создается как метод, компилятор переводит его во вложенный класс, который фактически является конечным автоматом. Данный класс отслеживает положения итератора, пока в клиентском коде выполняется цикл For Each...Next илиforeach.
Чтобы просмотреть операции компилятора, воспользуйтесь средством Ildasm.exe для отображения кода промежуточного языка Microsoft, создаваемого для метода итератора.
При создании итератора для класса или структуры реализация всего интерфейса IEnumerator не требуется. Когда компилятор обнаруживает итератор, он автоматически создает методы Current, MoveNext и Dispose интерфейса IEnumerator илиIEnumerator.
На каждой последовательной итерации цикла For Each…Next или foreach (или непосредственного вызова методаIEnumerator.MoveNext) код тела следующего итератора начинает выполнение после предыдущего оператора Yield или yield return. Затем он выполняется до следующего оператора Yield или yield return до тех пор, пока не будет достигнут конец тела итератора или пока не будет обнаружен оператор Visual Basic Exit Function или Return, или оператор C# yield break.
Итераторы не поддерживают метод IEnumerator.Reset. Для повторной итерации сначала необходимо получить новый итератор.
Дополнительные сведения см. в Спецификация языка Visual Basic или в Спецификация языка C#.
Использование итераторов
Итераторы позволяют поддерживать простоту цикла For Each, когда необходимо использовать сложный код, чтобы заполнить последовательность списков. Это может оказаться полезным при:
-
Изменении последовательности списков после первой итерации цикла For Each.
-
Избежании полной загрузки большого списка перед первой итерацией цикла For Each. Например, постраничная загрузка пакета строк таблицы. Другой пример — метод EnumerateFiles, который реализует итераторы в платформе .NET Framework.
-
Инкапсулировании построения списка в итераторе. В методе итератора можно построить список, а затем выдавать каждый результат в цикле.
Следующие C# блоги содержат дополнительные сведения об использовании итераторов.