Фактически, оператор yield
является синтаксическим сахаром , который заставляет компилятор фактически генерировать класс, который реализует интерфейс IEnumerator<T>
, и переписывает тело метода, используя оператор yield
, в конечный автомат.
Каждое состояние связано с частью кода, которая в конечном итоге генерирует следующий элемент в последовательности. Это встроено в метод MoveNext()
. Конечный автомат может представлять все необходимые конструкции (последовательность, выбор, итерация), и, следовательно, весь код C # (означающий операторы внутри метода) может быть переписан таким образом. Это основная «магия» yield
.
В вашем конкретном случае такое переписывание в конечный автомат и соответствующая полная реализация IEnumerator<T>
(и его унаследованных интерфейсов IEnumerator
(не универсальный) и IDisposable
) будет выглядеть следующим образом:
public class CustomEnumerator : IEnumerator<int>
{
public int Current { get; private set; }
object IEnumerator.Current => this.Current;
// internal 'position' in the sequence, i.e. the current state of the state machine
private int position = 0;
public bool MoveNext()
{
// advance to next state
// (works for linear algorithms; an alternative is to select the next state at the end of processing the current state)
position++;
// perform the code associated with the current state and produce an element
switch (position)
{
// state 1: line 'yield return 1;'
case 1:
Current = 1;
return true;
// state 2: lines 'Console.WriteLine("1");' and 'yield return 2;'
case 2:
Console.WriteLine("1"); // see also note at the end of this answer
Current = 2;
return true;
// there are no other states in this state machine
default:
return false;
}
}
public void Reset()
{
position = 0;
}
public void Dispose()
{
// nothing to do here
}
}
Каждый вызов MoveNext()
, который происходит внутри каждой итерации оператора foreach
, приводит к выполнению части кода до тех пор, пока не будет создан следующий элемент в последовательности.
Для использования этой реализации необходима соответствующая реализация IEnumerable<T>
, что довольно тривиально:
public class CustomEnumerable : IEnumerable<int>
{
public IEnumerator<int> GetEnumerator()
{
return new CustomEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
Тогда следующие два цикла foreach
будут давать точно такие же результаты:
void Main()
{
// custom implementation of IEnumerator<T>
foreach (int i in new CustomEnumerable())
{
Console.WriteLine(i);
}
// your original implementation—will produce same results
// note: I assume someObject implements IEnumerable<T> and hence your GetEnumerator() method
foreach (int i in someObject)
{
Console.WriteLine(i);
}
}
Примечание: в вашем GetEnumerator()
коде вызов Console.WriteLine("1");
равен после , перечислитель возвращает 1
(и вызывающий его обрабатывает), так что это выглядит немного странно.