Почему сгенерированный компилятором конечный автомат повторно восстанавливает состояние до -1? - PullRequest
0 голосов
/ 05 октября 2019

Я пытаюсь понять, как итераторы работают внутри, чтобы смягчить некоторые опасения, связанные с безопасностью потоков. Давайте рассмотрим, например, следующий простой итератор:

using System.Collections.Generic;

public class MyClass
{
    public static IEnumerable<int> MyMethod()
    {
        yield return 10;
        yield return 20;
        yield return 30;
    }
}

Я могу видеть созданный компилятором конечный автомат, который создается за кулисами, после копирования этого кода в SharpLab.io ,Это класс, который реализует интерфейсы IEnumerable<int> и IEnumerator<int> и содержит следующий метод MoveNext:

private bool MoveNext()
{
    switch (<>1__state)
    {
        default:
            return false;
        case 0:
            <>1__state = -1;
            <>2__current = 10;
            <>1__state = 1;
            return true;
        case 1:
            <>1__state = -1;
            <>2__current = 20;
            <>1__state = 2;
            return true;
        case 2:
            <>1__state = -1;
            <>2__current = 30;
            <>1__state = 3;
            return true;
        case 3:
            <>1__state = -1;
            return false;
    }
}

Идентификаторы <>1__state и <>2__currentчастные поля этого класса:

private int <>1__state;
private int <>2__current;

Я заметил закономерность в этом коде. Сначала значение поля <>1__state восстанавливается до -1, затем <>2__current присваивается следующему значению итерации, затем <>1__state переводится в следующее состояние. Мой вопрос: какова цель линии <>1__state = -1;? Я скомпилировал этот код (после мучительного переименования всех недопустимых идентификаторов) и подтвердил, что эта строка может быть закомментирована без ущерба для функциональности класса. Я не верю, что команда компилятора C # просто забыла об этом, казалось бы, бесполезном куске кода. Конечно, должна быть цель его существования, и я хотел бы знать, что это за цель.

1 Ответ

1 голос
/ 05 октября 2019

Не существует однозначного ответа на вопрос, зачем вам нужна переменная состояния и установите ее в -1 каждый раз, когда вы вводите свой оператор switch. Но я могу вспомнить один пример, в котором вам действительно понадобится переменная.

Как я уже говорил в разделе комментариев, компилятор не знает и не заботится о том, что делает <> 2__current.

Это может быть длительный веб-запрос на загрузку файла. Это может быть результат вычисления или просто целое число, как в вашем примере. Но здесь кроется проблема, потому что компилятор не знает, что делает ваш код, он может вызвать исключение. Давайте рассмотрим пример того, что произойдет, если вы пропустите переменную _state, и вы столкнетесь с исключением, пытающимся что-то загрузить.

1) MoveNext is called.
2) this.<>2_current = WebRequest.GetFileAsync() throws HttpRequestException.
3) The exception is caught somewhere and the execution of the program is resumed.
4) The caller invokes MoveNext method.
5) this.<>2_current = WebRequest.GetFileAsync() throws HttpRequestException

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

Когда мы введем переменную _state, результат будет сильно отличаться.

1) MoveNext is called.
2) this.<>2_current = WebRequest.GetFileAsync() throws HttpRequestException.
3) The exception is caught somewhere and execution of the program is resumed.
4) The caller invokes MoveNext method.

5) Since there’s no switch case for -1, the default block is reached which informs about the end of a sequence.
...