Реализация отчета о выходе

Я хочу знать все о инструкции yield в легко понятной форме.

Я прочитал инструкцию yield и ее легкость при реализации шаблона итератора. Однако большинство из них очень сухо. Я хотел бы получить под обложки и посмотреть, как Microsoft обрабатывает доход возврата.

Также, когда вы используете прерывание выхода?

Ответ 1

yield работает, создавая машину состояния внутри страны. Он сохраняет текущее состояние подпрограммы, когда он в следующий раз выходит из этого состояния и возобновляет его.

Вы можете использовать Reflector, чтобы увидеть, как он реализован компилятором.

yield break используется, когда вы хотите прекратить возвращать результаты. Если у вас нет yield break, компилятор будет считать один в конце функции (как оператор return; в нормальной функции)

Ответ 3

Как говорит Мехрдад, он создает конечный автомат.

Также, используя Reflector (другое превосходное предложение), вы можете найти мою статью о реализации блока итератора. Это было бы относительно просто, если бы не для блоков finally, но они ввели целый дополнительный размер сложности!

Ответ 4

Давайте немного перемотаем: ключевое слово yield переведено, как многие другие сказали конечному автомату.

На самом деле это не совсем похоже на использование встроенной реализации, которая будет использоваться за кулисами, а компилятор переписывает код, связанный с yield в конечный автомат, реализуя один из соответствующих интерфейсов (возвращаемый тип метода, содержащего ключевые слова yield).

(Конечный) конечный автомат - это просто кусок кода, который в зависимости от того, где вы находитесь в коде (в зависимости от предыдущего состояния, ввода), переходит к другому действию состояния, и это в значительной степени то, что происходит, когда вы используете и получаете с методом, возвращающим тип IEnumerator<T>/IEnumerator. Ключевое слово yield - это то, что собирается создать другое действие для перехода к следующему состоянию из предыдущего, поэтому управление состоянием создается в реализации MoveNext().

Именно это и собирается делать компилятор C/Roslyn: проверить наличие ключевого слова yield плюс тип возвращаемого значения содержащего метода, будь то IEnumerator<T>, IEnumerable<T>, IEnumerator или IEnumerable а затем создать закрытый класс, отражающий этот метод, объединяющий необходимые переменные и состояния.

Если вы заинтересованы в деталях того, как конечный автомат и как итерации переписываются компилятором, вы можете проверить эти ссылки на Github:

Общая информация 1: AsyncRewriter (используется при написании кода async/await также наследуется от StateMachineRewriter поскольку он также использует AsyncRewriter автомат.

Как упоминалось выше, состояние машины сильно отражается в bool MoveNext() генерируется выполнение в котором есть switch + иногда некоторые старомодный goto на основе состояния поля, которое представляет различные пути исполнения для различных состояний в вашем методе.

Код, сгенерированный компилятором из пользовательского кода, выглядит не очень хорошо, в основном потому, что компилятор добавляет некоторые странные префиксы и суффиксы тут и там

Например, код:

public class TestClass 
{
    private int _iAmAHere = 0;

    public IEnumerator<int> DoSomething()
    {
        var start = 1;
        var stop = 42;
        var breakCondition = 34;
        var exceptionCondition = 41;
        var multiplier = 2;
        // Rest of the code... with some yield keywords somewhere below...

Переменные и типы, связанные с этим фрагментом кода выше, после компиляции будут выглядеть так:

public class TestClass
{
    [CompilerGenerated]
    private sealed class <DoSomething>d__1 : IEnumerator<int>, IDisposable, IEnumerator
    {
        // Always present
        private int <>1__state;
        private int <>2__current;

        // Containing class
        public TestClass <>4__this;

        private int <start>5__1;
        private int <stop>5__2;
        private int <breakCondition>5__3;
        private int <exceptionCondition>5__4;
        private int <multiplier>5__5;

Что касается самого конечного автомата, давайте взглянем на очень простой пример с фиктивным ветвлением для получения некоторых четных/нечетных вещей.

public class Example
{
    public IEnumerator<string> DoSomething()
    {
        const int start = 1;
        const int stop = 42;

        for (var index = start; index < stop; index++)
        {
            yield return index % 2 == 0 ? "even" : "odd";
        }
    }
} 

Будет переведено в MoveNext как:

private bool MoveNext()
{
    switch (<>1__state)
    {
        default:
            return false;
        case 0:
            <>1__state = -1;
            <start>5__1 = 1;
            <stop>5__2 = 42;
            <index>5__3 = <start>5__1;
            break;
        case 1:
            <>1__state = -1;
            goto IL_0094;
        case 2:
            {
                <>1__state = -1;
                goto IL_0094;
            }
            IL_0094:
            <index>5__3++;
            break;
    }
    if (<index>5__3 < <stop>5__2)
    {
        if (<index>5__3 % 2 == 0)
        {
            <>2__current = "even";
            <>1__state = 1;
            return true;
        }
        <>2__current = "odd";
        <>1__state = 2;
        return true;
    }
    return false;
} 

Как видите, эта реализация далеко не прямолинейна, но она делает свою работу!

Общая информация 2. Что происходит с возвращаемым типом метода IEnumerable/IEnumerable<T>?
Что ж, вместо того, чтобы просто генерировать класс, реализующий IEnumerator<T>, он будет генерировать класс, который реализует как IEnumerable<T> так и IEnumerator<T> так что реализация IEnumerator<T> GetEnumerator() будет использовать тот же сгенерированный класс.

Теплое напоминание о нескольких интерфейсах, которые реализуются автоматически при использовании ключевого слова yield:

public interface IEnumerable<out T> : IEnumerable
{
    new IEnumerator<T> GetEnumerator();
}

public interface IEnumerator<out T> : IDisposable, IEnumerator
{
    T Current { get; }
}

public interface IEnumerator
{
    bool MoveNext();

    object Current { get; }

    void Reset();
}

Вы также можете проверить этот пример с другими путями/ветвлениями и полной реализацией путем переписывания компилятором.

Это было создано с SharpLab, вы можете поиграть с этим инструментом, чтобы попробовать разные пути выполнения, связанные с yield и посмотреть, как компилятор перезапишет их как MoveNext автомат в реализации MoveNext.

О второй части вопроса, т. yield break, ответили здесь

Это указывает, что итератор подошел к концу. Вы можете думать о yield break как о выражении возврата, которое не возвращает значение.