IteratorsIterators

Quase todos os programas que você escrever terão alguma necessidade de iterar em uma coleção.Almost every program you write will have some need to iterate over a collection. Você escreverá um código que examina cada item em uma coleção.You'll write code that examines every item in a collection.

Você também criará métodos iteradores que são métodos que produzem um iterador (que é um objeto que atravessa um contêiner, especialmente listas) para os elementos dessa classe.You'll also create iterator methods which are methods that produces an iterator (which is an object that traverses a container, particularly lists) for the elements of that class. Eles podem ser usados para:These can be used for:

  • Executar uma ação em cada item em uma coleção.Performing an action on each item in a collection.
  • Enumerar uma coleção personalizada.Enumerating a custom collection.
  • Estender LINQ ou outras bibliotecas.Extending LINQ or other libraries.
  • Criar um pipeline de dados em que os dados fluem com eficiência pelos métodos de iterador.Creating a data pipeline where data flows efficiently through iterator methods.

A linguagem C# fornece recursos para esses dois cenários.The C# language provides features for both these scenarios. Este artigo fornece uma visão geral desses recursos.This article provides an overview of those features.

Este tutorial tem várias etapas.This tutorial has multiple steps. Após cada etapa, você poderá executar o aplicativo e ver o progresso.After each step, you can run the application and see the progress. Você também pode exibir ou baixar o exemplo concluído deste tópico.You can also view or download the completed sample for this topic. Para obter instruções de download, consulte Exemplos e tutoriais.For download instructions, see Samples and Tutorials.

iterando com foreachIterating with foreach

Enumerar uma coleção é simples: a palavra-chave foreach enumera uma coleção, executando a instrução inserida uma vez para cada elemento na coleção:Enumerating a collection is simple: The foreach keyword enumerates a collection, executing the embedded statement once for each element in the collection:

foreach (var item in collection)
{
   Console.WriteLine(item.ToString());
}

Isso é tudo para ela.That's all there is to it. Para iterar em todo o conteúdo de uma coleção, a instrução foreach é tudo o que você precisa.To iterate over all the contents of a collection, the foreach statement is all you need. No entanto, a instrução foreach não é mágica.The foreach statement isn't magic, though. Ele se baseia em duas interfaces genéricas definidas na biblioteca do .NET Core para gerar o código necessário para iterar uma coleção: IEnumerable<T> e IEnumerator<T>.It relies on two generic interfaces defined in the .NET core library in order to generate the code necessary to iterate a collection: IEnumerable<T> and IEnumerator<T>. Esse mecanismo é explicado mais detalhadamente abaixo.This mechanism is explained in more detail below.

Essas duas interfaces também têm contrapartes não genéricas: IEnumerable e IEnumerator.Both of these interfaces also have non-generic counterparts: IEnumerable and IEnumerator. As versões genéricas são preferenciais para o código moderno.The generic versions are preferred for modern code.

Fontes de enumeração com métodos de iteradorEnumeration sources with iterator methods

Outro ótimo recurso da linguagem C# permite que você crie métodos que criam uma fonte para uma enumeração.Another great feature of the C# language enables you to build methods that create a source for an enumeration. Eles são chamados de métodos de iterador.These are referred to as iterator methods. Um método de iterador define como gerar os objetos em uma sequência quando solicitado.An iterator method defines how to generate the objects in a sequence when requested. Você usa as palavras-chave contextuais yield return para definir um método iterador.You use the yield return contextual keywords to define an iterator method.

Você poderia escrever esse método para produzir a sequência de inteiros de 0 a 9:You could write this method to produce the sequence of integers from 0 through 9:

public IEnumerable<int> GetSingleDigitNumbers()
{
    yield return 0;
    yield return 1;
    yield return 2;
    yield return 3;
    yield return 4;
    yield return 5;
    yield return 6;
    yield return 7;
    yield return 8;
    yield return 9;
}

O código acima mostra instruções yield return distintas para destacar o fato de que você pode usar várias instruções yield return discretas em um método iterador.The code above shows distinct yield return statements to highlight the fact that you can use multiple discrete yield return statements in an iterator method. Você pode (e frequentemente o faz) usar outros constructos de linguagem para simplificar o código de um método iterador.You can (and often do) use other language constructs to simplify the code of an iterator method. A definição do método abaixo produz a mesma sequência exata de números:The method definition below produces the exact same sequence of numbers:

public IEnumerable<int> GetSingleDigitNumbers()
{
    int index = 0;
    while (index < 10)
        yield return index++;
}

Você não precisa determinar uma ou a outra.You don't have to decide one or the other. Você pode ter quantas instruções yield return forem necessárias para atender as necessidades do seu método:You can have as many yield return statements as necessary to meet the needs of your method:

public IEnumerable<int> GetSingleDigitNumbers()
{
    int index = 0;
    while (index < 10)
        yield return index++;

    yield return 50;

    index = 100;
    while (index < 110)
        yield return index++;
}

Essa é a sintaxe básica.That's the basic syntax. Vamos considerar um exemplo do mundo real em que você escreveria um método iterador.Let's consider a real world example where you would write an iterator method. Imagine que você está em um projeto de IoT e os sensores de dispositivo geram um enorme fluxo de dados.Imagine you're on an IoT project and the device sensors generate a very large stream of data. Para ter uma noção dos dados, você pode escrever um método realiza a amostragem a cada N elementos de dados.To get a feel for the data, you might write a method that samples every Nth data element. Esse pequeno método iterador resolve:This small iterator method does the trick:

public static IEnumerable<T> Sample(this IEnumerable<T> sourceSequence, int interval)
{
    int index = 0;
    foreach (T item in sourceSequence)
    {
        if (index++ % interval == 0)
            yield return item;
    }
}

Há uma restrição importante em métodos de iterador: você não pode ter uma instrução return e uma instrução yield return no mesmo método.There is one important restriction on iterator methods: you can't have both a return statement and a yield return statement in the same method. O seguinte não compilará:The following will not compile:

public IEnumerable<int> GetSingleDigitNumbers()
{
    int index = 0;
    while (index < 10)
        yield return index++;

    yield return 50;

    // generates a compile time error:
    var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
    return items;
}

Essa restrição normalmente não é um problema.This restriction normally isn't a problem. Você tem a opção de usar yield return em todo o método ou separar o método original em vários métodos, alguns usando return e alguns usando yield return.You have a choice of either using yield return throughout the method, or separating the original method into multiple methods, some using return, and some using yield return.

Você pode modificar um pouco o último método para usar yield return em todos os lugares:You can modify the last method slightly to use yield return everywhere:

public IEnumerable<int> GetSingleDigitNumbers()
{
    int index = 0;
    while (index < 10)
        yield return index++;

    yield return 50;

    var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
    foreach (var item in items)
        yield return item;
}

Às vezes, a resposta certa é dividir um método iterador em dois métodos diferentes.Sometimes, the right answer is to split an iterator method into two different methods. Um que usa return e outro que usa yield return.One that uses return, and a second that uses yield return. Considere uma situação em que você talvez queira retornar uma coleção vazia ou os primeiros cinco números ímpares, com base em um argumento booliano.Consider a situation where you might want to return an empty collection, or the first 5 odd numbers, based on a boolean argument. Você poderia escrever isso como esses dois métodos:You could write that as these two methods:

public IEnumerable<int> GetSingleDigitOddNumbers(bool getCollection)
{
    if (getCollection == false)
        return new int[0];
    else
        return IteratorMethod();
}

private IEnumerable<int> IteratorMethod()
{
    int index = 0;
    while (index < 10)
    {
        if (index % 2 == 1)
            yield return index;
        index++;
    }
}

Observe os métodos acima.Look at the methods above. O primeiro usa a instrução return padrão para retornar uma coleção vazia ou o iterador criado pelo segundo método.The first uses the standard return statement to return either an empty collection, or the iterator created by the second method. O segundo método usa a instrução yield return para criar a sequência solicitada.The second method uses the yield return statement to create the requested sequence.

Aprofundamento em foreachDeeper Dive into foreach

A instrução foreach se expande em uma expressão padrão que usa as interfaces IEnumerable<T> e IEnumerator<T> para iterar em todos os elementos de uma coleção.The foreach statement expands into a standard idiom that uses the IEnumerable<T> and IEnumerator<T> interfaces to iterate across all elements of a collection. Ela também minimiza os erros cometidos pelos desenvolvedores por não gerenciarem os recursos adequadamente.It also minimizes errors developers make by not properly managing resources.

O compilador converte o loop foreach mostrado no primeiro exemplo em algo semelhante a esse constructo:The compiler translates the foreach loop shown in the first example into something similar to this construct:

IEnumerator<int> enumerator = collection.GetEnumerator();
while (enumerator.MoveNext())
{
    var item = enumerator.Current;
    Console.WriteLine(item.ToString());
}

O constructo acima representa o código gerado pelo compilador C# da versão 5 e posterior.The construct above represents the code generated by the C# compiler as of version 5 and above. Antes da versão 5, a variável item tinha um escopo diferente:Prior to version 5, the item variable had a different scope:

// C# versions 1 through 4:
IEnumerator<int> enumerator = collection.GetEnumerator();
int item = default(int);
while (enumerator.MoveNext())
{
    item = enumerator.Current;
    Console.WriteLine(item.ToString());
}

Isso foi alterado porque o comportamento anterior poderia levar a bugs sutis e difíceis de diagnosticar envolvendo expressões lambda.This was changed because the earlier behavior could lead to subtle and hard to diagnose bugs involving lambda expressions. Para saber mais sobre expressões lambda, confira o artigo sobre expressões lambda.For more information about lambda expressions, see Lambda expressions.

O código exato gerado pelo compilador é um pouco mais complicada e lida com situações em que o objeto retornado por GetEnumerator() implementa a interface IDisposable.The exact code generated by the compiler is somewhat more complicated, and handles situations where the object returned by GetEnumerator() implements the IDisposable interface. A expansão completa gera um código mais semelhante a esse:The full expansion generates code more like this:

{
    var enumerator = collection.GetEnumerator();
    try
    {
        while (enumerator.MoveNext())
        {
            var item = enumerator.Current;
            Console.WriteLine(item.ToString());
        }
    } finally
    {
        // dispose of enumerator.
    }
}

A maneira na qual o enumerador é descartado depende das características do tipo de enumerator.The manner in which the enumerator is disposed of depends on the characteristics of the type of enumerator. Em geral, a cláusula finally expande para:In the general case, the finally clause expands to:

finally
{
   (enumerator as IDisposable)?.Dispose();
}

No entanto, se o tipo de enumerator é um tipo lacrado e não há nenhuma conversão implícita do tipo de enumerator para IDisposable, a cláusula finally se expande para um bloco vazio:However, if the type of enumerator is a sealed type and there is no implicit conversion from the type of enumerator to IDisposable, the finally clause expands to an empty block:

finally
{
}

Se houver uma conversão implícita do tipo de enumerator para IDisposable e enumerator for um tipo de valor que não aceita valores nulos, a cláusula finally se expandirá para:If there is an implicit conversion from the type of enumerator to IDisposable, and enumerator is a non-nullable value type, the finally clause expands to:

finally
{
   ((IDisposable)enumerator).Dispose();
}

Felizmente, você não precisa se lembrar de todos esses detalhes.Thankfully, you don't need to remember all these details. A instrução foreach trata todas essas nuances para você.The foreach statement handles all those nuances for you. O compilador gerará o código correto para qualquer um desses constructos.The compiler will generate the correct code for any of these constructs.