Compartilhar via


Iteradores (C# e Visual Basic)

Um iterador pode ser usado para percorrer as coleções como listas e matrizes.

Um método de iterador ou um acessador de get executam uma iteração personalizado em uma coleção.Um método de iterador usa a declaração de Produzir (Visual Basic) ou de retorno de produzir (C#) para retornar um de cada vez a cada elemento.Quando uma declaração de Yield ou de yield return é alcançada, o local atual no código está recordado.A execução é reiniciada de aquele local na próxima vez que a função de iterador é chamada.

Você iterador consome um código do cliente usando uma instrução de For each… next (Visual Basic) ou de foreach (C#) ou usando uma consulta LINQ.

No exemplo a seguir, a primeira iteração do loop For Each ou foreach faz com que a execução continue no método de iterador SomeNumbers até a primeira instrução Yield ou yield return é alcançada.Essa interação retorna um valor de 3, e o local atual no método de iterador é mantido.Na próxima iteração do loop, a execução no método de iterador continua de onde parou novamente, parando quando atinge uma instrução de Yield ou de yield return .Essa interação retorna um valor de 5, e o local atual no método de iterador é mantido novamente.O loop termina quando o final do método de iterador é alcançado.

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
static void Main()
{
    foreach (int number in SomeNumbers())
    {
        Console.Write(number.ToString() + " ");
    }
    // Output: 3 5 8
    Console.ReadKey();
}

public static System.Collections.IEnumerable SomeNumbers()
{
    yield return 3;
    yield return 5;
    yield return 8;
}

O tipo de retorno de um método de iterador ou um acessador de get pode ser IEnumerable, IEnumerable<T>, IEnumerator, ou IEnumerator<T>.

Você pode usar uma instrução de Exit Function ou de Return (Visual Basic) ou a declaração de yield break (C#) para finalizar a iteração.

Uma função de iterador de Visual Basic ou uma declaração de assessor de get incluem um modificador de Iterador .

Iteradores foram introduzidos em C# em Visual Studio 2005, e introduzidos em Visual Basic em Visual Studio 2012.

Neste tópico

  • Iterador simples

  • Criando uma classe de coleção

  • Blocos try no Visual Basic

  • Métodos anônimos no Visual Basic

  • Usando iteradores com uma lista genérica

  • Informações de sintaxe

  • Implementação técnica

  • Uso de iteradores

Iterador simples

O exemplo tem uma única Yield ou instrução yield return que está dentro de um loop de For… Next (Visual Basic) ou de para (C#).Em Main, cada iteração do corpo da instrução For Each ou foreach cria uma chamada para a função de iterador, que continua para a próxima instrução Yield ou yield return.

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
static void Main()
{
    foreach (int number in EvenSequence(5, 18))
    {
        Console.Write(number.ToString() + " ");
    }
    // Output: 6 8 10 12 14 16 18
    Console.ReadKey();
}

public static System.Collections.Generic.IEnumerable<int>
    EvenSequence(int firstNumber, int lastNumber)
{
    // Yield even numbers in the range.
    for (int number = firstNumber; number <= lastNumber; number++)
    {
        if (number % 2 == 0)
        {
            yield return number;
        }
    }
}

Criando uma classe de coleção

ObservaçãoObservação

Para outros exemplos, no tópico inclui instruções de Imports (Visual Basic) ou diretivas de usando (C#) para System.Collections e namespaces de System.Collections.Generic .

No exemplo, a classe de DaysOfTheWeek implementa a interface de IEnumerable , que requer um método de GetEnumerator .O compilador chama implicitamente o método de GetEnumerator , que retorna IEnumerator.

O método de GetEnumerator retorna cada cadeia de caracteres de um cada vez usando a instrução de Yield ou de yield return .No código de Visual Basic , um modificador de Iterator está na declaração da função.

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
static void Main()
{
    DaysOfTheWeek days = new DaysOfTheWeek();

    foreach (string day in days)
    {
        Console.Write(day + " ");
    }
    // Output: Sun Mon Tue Wed Thu Fri Sat
    Console.ReadKey();
}

public class DaysOfTheWeek : IEnumerable
{
    private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

    public IEnumerator GetEnumerator()
    {
        for (int index = 0; index < days.Length; index++)
        {
            // Yield each day of the week.
            yield return days[index];
        }
    }
}

O exemplo a seguir cria uma classe de Zoo que contém uma coleção de animais.

A instrução For Each ou foreach referindo-se à instância da classe (theZoo) implicitamente chama o método GetEnumerator.As instruções For Each ou foreach que referenciam as propriedades Birds e Mammals usam o método de iterador AnimalsForType chamado.

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
static void Main()
{
    Zoo theZoo = new Zoo();

    theZoo.AddMammal("Whale");
    theZoo.AddMammal("Rhinoceros");
    theZoo.AddBird("Penguin");
    theZoo.AddBird("Warbler");

    foreach (string name in theZoo)
    {
        Console.Write(name + " ");
    }
    Console.WriteLine();
    // Output: Whale Rhinoceros Penguin Warbler

    foreach (string name in theZoo.Birds)
    {
        Console.Write(name + " ");
    }
    Console.WriteLine();
    // Output: Penguin Warbler

    foreach (string name in theZoo.Mammals)
    {
        Console.Write(name + " ");
    }
    Console.WriteLine();
    // Output: Whale Rhinoceros

    Console.ReadKey();
}

public class Zoo : IEnumerable
{
    // Private members.
    private List<Animal> animals = new List<Animal>();

    // Public methods.
    public void AddMammal(string name)
    {
        animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Mammal });
    }

    public void AddBird(string name)
    {
        animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Bird });
    }

    public IEnumerator GetEnumerator()
    {
        foreach (Animal theAnimal in animals)
        {
            yield return theAnimal.Name;
        }
    }

    // Public members.
    public IEnumerable Mammals
    {
        get { return AnimalsForType(Animal.TypeEnum.Mammal); }
    }

    public IEnumerable Birds
    {
        get { return AnimalsForType(Animal.TypeEnum.Bird); }
    }

    // Private methods.
    private IEnumerable AnimalsForType(Animal.TypeEnum type)
    {
        foreach (Animal theAnimal in animals)
        {
            if (theAnimal.Type == type)
            {
                yield return theAnimal.Name;
            }
        }
    }

    // Private class.
    private class Animal
    {
        public enum TypeEnum { Bird, Mammal }

        public string Name { get; set; }
        public TypeEnum Type { get; set; }
    }
}

Blocos try no Visual Basic

Visual Basic permite uma instrução de Yield no bloco de Try de Try...Catch...Instrução finally (Visual Basic).Um bloco de Try que tem uma declaração de Yield pode ter blocos de Catch , e pode ter um bloco de Finally .

Anotação de C++Anotação de C#

Permite C# uma instrução de yield return no bloco de try de uma instrução de tente - final .Um bloco de try que tem uma declaração de yield return não pode ter quaisquer blocos de catch .

O exemplo de Visual Basic inclui Try, Catch, e os blocos de Finally em um iterador funcionam.O bloco de Finally na função de iterador executa antes da iteração de For Each terminar.

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

Uma declaração de Yield não pode ser dentro de um bloco de Catch ou um bloco de Finally .

Se o corpo de For Each (em vez do método de iterador) gera uma exceção, um bloco de Catch na função de iterador não é executado, mas um bloco de Finally na função de iterador é executado.Um bloco de Catch dentro de uma função de iterador somente captura exceções que ocorrem dentro da função de iterador.

Métodos anônimos no Visual Basic

Em Visual Basic (mas não em C#), uma função anônimo pode ser uma função de iterador.O exemplo a seguir ilustra isto:

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

O exemplo de Visual Basic tem um método que não são de iterador que valida os argumentos.O método retorna o resultado de um iterador anônimo que descreve os elementos da coleção.

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

Se a validação é o lugar dentro da função de iterador, a validação não pode ser executada até o início da primeira iteração do corpo de For Each .

Usando iteradores com uma lista genérica

No exemplo, a classe genérica de Stack(Of T) implementa a interface genérica de IEnumerable<T> .O método de Push atribui valores a uma matriz do tipo T.O método de GetEnumerator retorna os valores de matriz usando a instrução de Yield ou de yield return .

Além do método genérico de GetEnumerator , o método não genéricos de GetEnumerator também deve ser implementado.Isso ocorre porque IEnumerable<T> herda de IEnumerable.A implementação não genéricos adia para a implementação genérico.

O exemplo usa iteradores nomeados para suportar várias maneiras para iterar através da mesma coleção de dados.Esses iteradores nomeados são as propriedades de TopToBottom e de BottomToTop , e o método de TopN .

A propriedade de BottomToTop usa um iterador em um acessador de get .No código de Visual Basic , a declaração de propriedade inclui a palavra-chave de 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
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];
        }
    }

}

Informações de sintaxe

Um iterador pode ocorrer como um método ou um acessador de get .Um iterador não pode ocorrer em um evento, em um construtor de instância, em um construtor estático, ou em um destrutor estático.

Uma conversão implícita de expressão deve existir na declaração de Yield (Visual Basic) ou de yield return (C#) para o tipo de retorno de iterador.

Em Visual Basic, um método de iterador pode não ter nenhum parâmetro de ByRef .Em C#, um método de iterador pode não ter nenhum parâmetro de ref ou de out .

Em Visual Basic, “produzir” não é uma palavra reservada e tem um significado especial somente quando é usado em um método de Iterator ou em um acessador de get .Em C#, “produzir” não é uma palavra reservada e tem um significado especial somente quando é usada antes de uma palavra-chave de return ou de break .

Implementação técnica

Embora você escreva um iterador como um método, o compilador converte em uma classe aninhada, isso é verdade, um computador de estado.Essa classe mantém registro da posição de iterador como tempo For Each...Next ou loop de foreach no código do cliente continua.

Para ver o que o compilador isso, você pode usar a ferramenta de Ildasm.exe para exibir o código da Microsoft intermediate language que é gerado para um método de iterador.

Quando você cria um iterador para classe ou estrutura, você não tem que implementar a interface inteira de IEnumerator .Quando o compilador detecta o iterador, gera automaticamente Current, MoveNext, os métodos e de Dispose de interface de IEnumerator ou de IEnumerator<T> .

Em cada iteração do loop sucessiva de For Each…Next ou de foreach (ou de chamada direto a IEnumerator.MoveNext), nos seguintes resumos do corpo de código de iterador após Yield ou a instrução anterior de yield return .Depois continuará a seguir Yield ou a declaração de yield return do corpo de iterador é alcançada até o final, ou até que uma declaração de Exit Function ou de Return (Visual Basic) ou a declaração de yield break (C#) é encontrado.

Iteradores não suportam o método de IEnumerator.Reset .Para reiterar do início, você deve obter um novo iterador.

Para obter mais informações, consulte o Especificação de linguagem Visual Basic ou Especificação de linguagem do C#.

Uso de iteradores

Iteradores permite que você mantenha a simplicidade de um loop de For Each quando você precisará usar o código complexo para preencher uma sequência da lista.Isso pode ser útil quando você deseja fazer o seguinte:

  • Modifique a sequência de lista após a primeira iteração do loop de For Each .

  • Evite totalmente carregar uma grande lista antes que a primeira iteração de um loop de For Each .Um exemplo é a um esforço paginado para carregar um lote de linhas da tabela.Um exemplo é o método de EnumerateFiles , que implementa iteradores dentro do .NET Framework.

  • Encapsular compilar a lista no iterador.No método de iterador, você pode compilar a lista e então produzir cada resultado de um loop.

Os seguintes blogs C# fornecem informações adicionais sobre o uso de iteradores.

Consulte também

Referência

Para cada...Próxima instrução (Visual Basic)

foreach, (C# Reference)

Instrução yield (Visual Basic)

rendimento (referência de C#)

Iterador (Visual Basic)

O uso de foreach com matrizes (guia de programação C#)

Genéricos (guia de programação do C#)

System.Collections.Generic

IEnumerable<T>