Итераторы (C#)Iterators (C#)

Итератор можно использовать для прохода по коллекции, такой как список или массив.An iterator can be used to step through collections such as lists and arrays.

Метод итератора или метод доступа get выполняет настраиваемую итерацию по коллекции.An iterator method or get accessor performs a custom iteration over a collection. Метод итератора использует оператор yield return для поочередного возврата каждого элемента.An iterator method uses the yield return statement to return each element one at a time. При достижении инструкции yield return текущее расположение в коде запоминается.When a yield return statement is reached, the current location in code is remembered. При следующем вызове функции итератора выполнение возобновляется с этого места.Execution is restarted from that location the next time the iterator function is called.

Итератор используется из клиентского кода с помощью оператора foreach или с помощью запроса LINQ.You consume an iterator from client code by using a foreach statement or by using a LINQ query.

В приведенном ниже примере первая итерация цикла foreach приводит к вызову метода итератора SomeNumbers, пока не будет достигнут первый оператор yield return.In the following example, the first iteration of the foreach loop causes execution to proceed in the SomeNumbers iterator method until the first yield return statement is reached. Эта итерация возвращает значение 3. Текущее расположение в методе итератора сохраняется.This iteration returns a value of 3, and the current location in the iterator method is retained. В следующей итерации цикла выполнение метода итератора возобновляется с того же места и снова приостанавливается при достижении оператора yield return.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 return statement. Эта итерация возвращает значение 5. Текущее расположение в методе итератора снова сохраняется.This iteration returns a value of 5, and the current location in the iterator method is again retained. Цикл завершается при достижении конца метода итератора.The loop completes when the end of the iterator method is reached.

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;
}

Типом возвращаемого метода итератора или метода доступа get может быть IEnumerable, IEnumerable<T>, IEnumerator или IEnumerator<T>.The return type of an iterator method or get accessor can be IEnumerable, IEnumerable<T>, IEnumerator, or IEnumerator<T>.

Для завершения итерации можно использовать оператор yield break.You can use a yield break statement to end the iteration.

Примечание

Все примеры в этом разделе, кроме примера простого итератора, включают директивы using для пространств имен System.Collections и System.Collections.Generic.For all examples in this topic except the Simple Iterator example, include using directives for the System.Collections and System.Collections.Generic namespaces.

Простой итераторSimple Iterator

В следующем примере имеется один оператор yield return, который находится внутри цикла for.The following example has a single yield return statement that is inside a for loop. В методе Main каждая итерация оператора foreach создает вызов функции итератора, которая выполняет следующий оператор yield return.In Main, each iteration of the foreach statement body creates a call to the iterator function, which proceeds to the next yield return statement.

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;
        }
    }
}

Создание класса коллекцииCreating a Collection Class

В следующем примере класс DaysOfTheWeek реализует интерфейс IEnumerable, которому требуется метод GetEnumerator.In the following example, the DaysOfTheWeek class implements the IEnumerable interface, which requires a GetEnumerator method. Компилятор неявно вызывает метод GetEnumerator, который возвращает IEnumerator.The compiler implicitly calls the GetEnumerator method, which returns an IEnumerator.

Метод GetEnumerator возвращает каждую строку поочередно с помощью оператора yield return.The GetEnumerator method returns each string one at a time by using the yield return statement.

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];
        }
    }
}

В приведенном ниже примере создается класс Zoo, содержащий коллекцию животных.The following example creates a Zoo class that contains a collection of animals.

Оператор foreach, который обращается к экземпляру класса (theZoo), неявно вызывает метод GetEnumerator.The foreach statement that refers to the class instance (theZoo) implicitly calls the GetEnumerator method. Операторы foreach, которые обращаются к свойствам Birds и Mammals, используют метод итератора с именем AnimalsForType.The foreach statements that refer to the Birds and Mammals properties use the AnimalsForType named iterator method.

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; }
    }
}

Использование итераторов с универсальным спискомUsing Iterators with a Generic List

В следующем примере универсальный класс Stack<T> также реализует универсальный интерфейс IEnumerable<T>.In the following example, the Stack<T> generic class implements the IEnumerable<T> generic interface. Метод Push присваивает значения массиву типа T.The Push method assigns values to an array of type T. Метод GetEnumerator возвращает массив значений с помощью оператора yield return.The GetEnumerator method returns the array values by using the yield return statement.

Помимо универсального метода GetEnumerator должен быть реализован и неуниверсальный метод GetEnumerator.In addition to the generic GetEnumerator method, the non-generic GetEnumerator method must also be implemented. Это связано с тем, что IEnumerable<T> наследуется от IEnumerable.This is because IEnumerable<T> inherits from IEnumerable. Неуниверсальная реализация подчиняется универсальной реализации.The non-generic implementation defers to the generic implementation.

В этом примере используются именованные итераторы для поддержки различных способов итерации по одной и той же коллекции данных.The example uses named iterators to support various ways of iterating through the same collection of data. Эти именованные итераторы являются свойствами TopToBottom и BottomToTop и методом TopN.These named iterators are the TopToBottom and BottomToTop properties, and the TopN method.

Свойство BottomToTop использует итератор в методе доступа get.The BottomToTop property uses an iterator in a get accessor.

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];
        }
    }

}

Сведения о синтаксисеSyntax Information

Итератор может являться методом или методом доступа get.An iterator can occur as a method or get accessor. Итератор не может использоваться в событии, конструкторе экземпляра, статическом конструкторе или статическом методе завершения.An iterator cannot occur in an event, instance constructor, static constructor, or static finalizer.

Должно существовать неявное преобразование типа выражения в операторе yield return в аргумент типа для IEnumerable<T>, возвращаемого итератором.An implicit conversion must exist from the expression type in the yield return statement to the type argument for the IEnumerable<T> returned by the iterator.

В C# метод итератора не может иметь параметры in, ref или out.In C#, an iterator method cannot have any in, ref, or out parameters.

В C# yield не является зарезервированным словом и имеет специальное значение, только если используется перед ключевым словом return или break.In C#, yield is not a reserved word and has special meaning only when it is used before a return or break keyword.

Техническая реализацияTechnical Implementation

Хотя итератор создается как метод, компилятор переводит его во вложенный класс, который фактически является конечным автоматом.Although you write an iterator as a method, the compiler translates it into a nested class that is, in effect, a state machine. Этот класс отслеживает положение итератора, пока в клиентском коде выполняется цикл foreach.This class keeps track of the position of the iterator as long the foreach loop in the client code continues.

Чтобы просмотреть операции компилятора, воспользуйтесь средством Ildasm.exe, чтобы просмотреть код промежуточного языка Майкрософт, создаваемый для метода итератора.To see what the compiler does, you can use the Ildasm.exe tool to view the Microsoft intermediate language code that's generated for an iterator method.

При создании итератора для класса или структуры реализация всего интерфейса IEnumerator не требуется.When you create an iterator for a class or struct, you don't have to implement the whole IEnumerator interface. Когда компилятор обнаруживает итератор, он автоматически создает методы Current, MoveNext и Dispose интерфейса IEnumerator или IEnumerator<T>.When the compiler detects the iterator, it automatically generates the Current, MoveNext, and Dispose methods of the IEnumerator or IEnumerator<T> interface.

В каждой последовательной итерации цикла foreach (или непосредственном вызове метода IEnumerator.MoveNext) код тела следующего итератора возобновляет выполнение после предыдущего оператора yield return.On each successive iteration of the foreach loop (or the direct call to IEnumerator.MoveNext), the next iterator code body resumes after the previous yield return statement. Затем он выполняется до следующего оператора yield return до тех пор, пока не будет достигнут конец тела итератора или пока не будет обнаружен оператор yield break.It then continues to the next yield return statement until the end of the iterator body is reached, or until a yield break statement is encountered.

Итераторы не поддерживают метод IEnumerator.Reset.Iterators don't support the IEnumerator.Reset method. Для повторной итерации сначала необходимо получить новый итератор.To reiterate from the start, you must obtain a new iterator. Вызов Reset в итераторе, который возвращается методом итератора, вызывает исключение NotSupportedException.Calling Reset on the iterator returned by an iterator method throws a NotSupportedException.

Дополнительные сведения см. в спецификации языка C#.For additional information, see the C# Language Specification.

Использование итераторовUse of Iterators

Итераторы позволяют поддерживать простоту цикла foreach, когда необходимо использовать сложный код для заполнения последовательности списков.Iterators enable you to maintain the simplicity of a foreach loop when you need to use complex code to populate a list sequence. Это может оказаться полезным в следующих случаях:This can be useful when you want to do the following:

  • Изменение последовательности списков после первой итерации цикла foreach.Modify the list sequence after the first foreach loop iteration.

  • Если необходимо избежать полной загрузки большого списка перед первой итерацией цикла foreach.Avoid fully loading a large list before the first iteration of a foreach loop. Пример: при постраничной загрузке пакета строк таблицы.An example is a paged fetch to load a batch of table rows. Другой пример — метод EnumerateFiles, реализующий итераторы в .NET.Another example is the EnumerateFiles method, which implements iterators in .NET.

  • Инкапсулирование построения списка в итераторе.Encapsulate building the list in the iterator. В методе итератора можно построить список, а затем выдавать каждый результат в цикле.In the iterator method, you can build the list and then yield each result in a loop.

См. такжеSee also