Как реализовать шаблон Iterator (IEnumerator <T>) без оператора yield - PullRequest
0 голосов
/ 12 мая 2018

Как можно переписать метод GetEnumerator без использования ключевого слова yield? Код метода:

public IEnumerator<int> GetEnumerator()
{
    yield return 1;

    Console.WriteLine("1");

    yield return 2;
}

Я только что знаю, как это можно реализовать вручную.

1 Ответ

0 голосов
/ 12 мая 2018

Фактически, оператор 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 (и вызывающий его обрабатывает), так что это выглядит немного странно.

...