Iteradores (Visual Basic)Iterators (Visual Basic)

Un iterador puede usarse para recorrer colecciones como listas y matrices.An iterator can be used to step through collections such as lists and arrays.

Un método iterador o un descriptor de acceso get realiza una iteración personalizada en una colección.An iterator method or get accessor performs a custom iteration over a collection. Un método de iterador utiliza la instrucción yield para devolver cada elemento de uno en uno.An iterator method uses the Yield statement to return each element one at a time. Cuando se alcanza una instrucción Yield, se recuerda la ubicación actual en el código.When a Yield statement is reached, the current location in code is remembered. La ejecución se reinicia desde esa ubicación la próxima vez que se llama a la función del iterador.Execution is restarted from that location the next time the iterator function is called.

Un iterador se usa a partir del código de cliente mediante un para cada... Instrucción siguiente o mediante una consulta LINQ.You consume an iterator from client code by using a For Each…Next statement, or by using a LINQ query.

En el ejemplo siguiente, la primera iteración del bucle For Each hace que continúe la ejecución del método de iterador SomeNumbers hasta que se alcance la primera instrucción Yield.In the following example, the first iteration of the For Each loop causes execution to proceed in the SomeNumbers iterator method until the first Yield statement is reached. Esta iteración devuelve un valor de 3, y la ubicación actual del método de iterador se conserva.This iteration returns a value of 3, and the current location in the iterator method is retained. En la siguiente iteración del bucle, la ejecución del método iterador continúa desde donde se dejó, deteniéndose de nuevo al alcanzar una instrucción Yield.On the next iteration of the loop, execution in the iterator method continues from where it left off, again stopping when it reaches a Yield statement. Esta iteración devuelve un valor de 5, y la ubicación actual del método de iterador se vuelve a conservar.This iteration returns a value of 5, and the current location in the iterator method is again retained. El bucle se completa al alcanzar el final del método iterador.The loop completes when the end of the iterator method is reached.

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

El tipo de valor devuelto de un método de iterador o descriptor de acceso get puede ser IEnumerable, IEnumerable<T>, IEnumerator o IEnumerator<T>.The return type of an iterator method or get accessor can be IEnumerable, IEnumerable<T>, IEnumerator, or IEnumerator<T>.

Puede usar una instrucción Exit Function o Return para finalizar la iteración.You can use an Exit Function or Return statement to end the iteration.

Una Visual Basic función de iterador o get declaración de descriptor de acceso incluye un modificador de iterador .A Visual Basic iterator function or get accessor declaration includes an Iterator modifier.

Los iteradores se introdujeron en Visual Basic en Visual Studio 2012.Iterators were introduced in Visual Basic in Visual Studio 2012.

En este temaIn this topic

Nota

En todos los ejemplos del tema excepto en el ejemplo de iterador simple, incluya instrucciones Imports para los espacios de nombres System.Collections y System.Collections.Generic.For all examples in the topic except the Simple Iterator example, include Imports statements for the System.Collections and System.Collections.Generic namespaces.

Iterador simpleSimple Iterator

El ejemplo siguiente tiene una única instrucción Yield que está dentro de un ... Siguiente bucle.The following example has a single Yield statement that is inside a For…Next loop. En Main, cada iteración del cuerpo de la instrucción For Each crea una llamada a la función de iterador, que continúa a la instrucción Yield siguiente.In Main, each iteration of the For Each statement body creates a call to the iterator function, which proceeds to the next Yield statement.

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

Crear una clase de colecciónCreating a Collection Class

En el ejemplo siguiente, la clase DaysOfTheWeek implementa la interfaz IEnumerable, que requiere un método GetEnumerator.In the following example, the DaysOfTheWeek class implements the IEnumerable interface, which requires a GetEnumerator method. El compilador llama implícitamente al método GetEnumerator, que devuelve un IEnumerator.The compiler implicitly calls the GetEnumerator method, which returns an IEnumerator.

El método GetEnumerator devuelve cada cadena de una en una mediante la instrucción Yield y un modificador Iterator se encuentra en la declaración de función.The GetEnumerator method returns each string one at a time by using the Yield statement, and an Iterator modifier is in the function declaration.

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

En el ejemplo siguiente se crea una clase Zoo que contiene una colección de animales.The following example creates a Zoo class that contains a collection of animals.

La instrucción For Each que hace referencia a la instancia de clase (theZoo) llama implícitamente al método GetEnumerator.The For Each statement that refers to the class instance (theZoo) implicitly calls the GetEnumerator method. Las instrucciones For Each que hacen referencia a las propiedades Birds y Mammals usan el método iterador con el nombre AnimalsForType.The For Each statements that refer to the Birds and Mammals properties use the AnimalsForType named iterator method.

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

Bloques tryTry Blocks

Visual Basic permite una instrucción de Yield en el bloque Try de una instrucción try... Detectar... Finally.Visual Basic allows a Yield statement in the Try block of a Try...Catch...Finally Statement. Un bloque Try que tiene una instrucción Yield puede tener bloques Catch y puede tener un bloque Finally.A Try block that has a Yield statement can have Catch blocks, and can have a Finally block.

En el ejemplo siguiente se incluyen los bloques Try, Catch y Finally en una función de iterador.The following example includes Try, Catch, and Finally blocks in an iterator function. El bloque Finally de la función de iterador se ejecuta antes de que finalice la iteración de For Each.The Finally block in the iterator function executes before the For Each iteration finishes.

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

Una instrucción Yield no puede estar dentro de un bloque de Catch o un bloque Finally.A Yield statement cannot be inside a Catch block or a Finally block.

Si el cuerpo de For Each (en lugar del método de iterador) produce una excepción, no se ejecuta un bloque de Catch en la función de iterador, pero se ejecuta un bloque de Finally en la función de iterador.If the For Each body (instead of the iterator method) throws an exception, a Catch block in the iterator function is not executed, but a Finally block in the iterator function is executed. Un bloque Catch dentro de una función de iterador solo detecta las excepciones que se producen dentro de la función de iterador.A Catch block inside an iterator function catches only exceptions that occur inside the iterator function.

Métodos anónimosAnonymous Methods

En Visual Basic, una función anónima puede ser una función de iterador.In Visual Basic, an anonymous function can be an iterator function. Esto se ilustra en el siguiente ejemplo:The following example illustrates this.

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

En el ejemplo siguiente hay un método que no es de iterador que valida los argumentos.The following example has a non-iterator method that validates the arguments. El método devuelve el resultado de un iterador anónimo que describe los elementos de la colección.The method returns the result of an anonymous iterator that describes the collection elements.

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

Si la validación está en su lugar dentro de la función de iterador, no se puede realizar la validación hasta el inicio de la primera iteración del cuerpo de For Each.If validation is instead inside the iterator function, the validation cannot be performed until the start of the first iteration of the For Each body.

Uso de iteradores con una lista genéricaUsing Iterators with a Generic List

En el ejemplo siguiente, la clase genérica Stack(Of T) implementa la interfaz genérica IEnumerable<T>.In the following example, the Stack(Of T) generic class implements the IEnumerable<T> generic interface. El método Push asigna valores a una matriz de tipo T.The Push method assigns values to an array of type T. El método GetEnumerator devuelve los valores de la matriz con la instrucción Yield.The GetEnumerator method returns the array values by using the Yield statement.

Además del método GetEnumerator genérico, el método GetEnumerator no genérico también debe implementarse.In addition to the generic GetEnumerator method, the non-generic GetEnumerator method must also be implemented. Esto es porque IEnumerable<T> se hereda de IEnumerable.This is because IEnumerable<T> inherits from IEnumerable. La implementación no genérica aplaza la implementación genérica.The non-generic implementation defers to the generic implementation.

El ejemplo usa iteradores con nombre para admitir distintas formas de recorrer en iteración la misma colección de datos.The example uses named iterators to support various ways of iterating through the same collection of data. Estos iteradores con nombre son las propiedades TopToBottom y BottomToTop, y el método TopN.These named iterators are the TopToBottom and BottomToTop properties, and the TopN method.

La declaración de la propiedad BottomToTop incluye la palabra clave Iterator.The BottomToTop property declaration includes the Iterator keyword.

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

Información sobre la sintaxisSyntax Information

Un iterador se puede producir como un método o como un descriptor de acceso get.An iterator can occur as a method or get accessor. Un iterador no puede aparecer en un evento, un constructor de instancia, un constructor estático o un destructor estático.An iterator cannot occur in an event, instance constructor, static constructor, or static destructor.

Debe existir una conversión implícita desde el tipo de expresión en la instrucción Yield al tipo de valor devuelto por el iterador.An implicit conversion must exist from the expression type in the Yield statement to the return type of the iterator.

En Visual Basic, un método de iterador no puede tener ningún parámetro ByRef.In Visual Basic, an iterator method cannot have any ByRef parameters.

En Visual Basic, "yield" no es una palabra reservada y tiene un significado especial solo cuando se usa en un método de Iterator o un descriptor de acceso get.In Visual Basic, "Yield" is not a reserved word and has special meaning only when it is used in an Iterator method or get accessor.

Implementación técnicaTechnical Implementation

Aunque un iterador se escribe como un método, el compilador lo traduce a una clase anidada que es, en realidad, una máquina de estados.Although you write an iterator as a method, the compiler translates it into a nested class that is, in effect, a state machine. Esta clase realiza el seguimiento de la posición del iterador mientras el bucle For Each...Next continúe en el código de cliente.This class keeps track of the position of the iterator as long the For Each...Next loop in the client code continues.

Para ver lo que hace el compilador, puede usar la herramienta Ildasm.exe para ver el código de lenguaje intermedio de Microsoft que se genera para un método iterador.To see what the compiler does, you can use the Ildasm.exe tool to view the Microsoft intermediate language code that is generated for an iterator method.

Cuando se crea un iterador para una clase o struct, no es necesario implementar la interfaz de IEnumerator completa.When you create an iterator for a class or struct, you do not have to implement the whole IEnumerator interface. Cuando el compilador detecta el iterador, genera automáticamente los métodos Current, MoveNext y Dispose de la interfaz IEnumerator o IEnumerator<T>.When the compiler detects the iterator, it automatically generates the Current, MoveNext, and Dispose methods of the IEnumerator or IEnumerator<T> interface.

En cada iteración sucesiva del bucle For Each…Next (o la llamada directa a IEnumerator.MoveNext), el cuerpo de código del iterador siguiente se reanuda después de la instrucción Yield anterior.On each successive iteration of the For Each…Next loop (or the direct call to IEnumerator.MoveNext), the next iterator code body resumes after the previous Yield statement. Después, continúa con la siguiente instrucción Yield hasta que se alcanza el final del cuerpo del iterador o hasta que se encuentra una instrucción Exit Function o Return.It then continues to the next Yield statement until the end of the iterator body is reached, or until an Exit Function or Return statement is encountered.

Los iteradores no admiten el método IEnumerator.Reset.Iterators do not support the IEnumerator.Reset method. Para volver a recorrer en iteración desde el principio, se debe obtener un nuevo iterador.To re-iterate from the start, you must obtain a new iterator.

Para obtener más información, vea la especificación del lenguaje Visual Basic.For additional information, see the Visual Basic Language Specification.

Uso de iteradoresUse of Iterators

Los iteradores permiten mantener la simplicidad de un bucle For Each cuando se necesita usar código complejo para rellenar una secuencia de lista.Iterators enable you to maintain the simplicity of a For Each loop when you need to use complex code to populate a list sequence. Esto puede ser útil si quiere hacer lo siguiente:This can be useful when you want to do the following:

  • Modificar la secuencia de lista después de la primera iteración del bucle For Each.Modify the list sequence after the first For Each loop iteration.

  • Evitar que se cargue totalmente una lista grande antes de la primera iteración de un bucle For Each.Avoid fully loading a large list before the first iteration of a For Each loop. Un ejemplo es una búsqueda paginada para cargar un lote de filas de tabla.An example is a paged fetch to load a batch of table rows. Otro ejemplo es el método EnumerateFiles, que implementa iteradores en .NET Framework.Another example is the EnumerateFiles method, which implements iterators within the .NET Framework.

  • Encapsular la creación de la lista en el iterador.Encapsulate building the list in the iterator. En el método iterador, puede crear la lista y después devolver cada resultado en un bucle.In the iterator method, you can build the list and then yield each result in a loop.

Vea tambiénSee also