Итераторы (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 firstforeach
loop iteration.Если необходимо избежать полной загрузки большого списка перед первой итерацией цикла
foreach
.Avoid fully loading a large list before the first iteration of aforeach
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.