Как урожай реализует шаблон ленивой загрузки?

Как yield реализует шаблон lazy loading?

9
задан masoud ramezani 3 May 2010 в 10:16
поделиться

3 ответа

реализация yield не доходит до кода, пока он не понадобится.

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

public IEnumerable<int> GetInts()
{
    yield return 1;
    yield return 2;
    yield return 3;
}

В действительности скомпилируется во вложенный класс, который реализует IEnumerable и тело GetInts() вернет экземпляр этого класса.

Используя reflector, вы можете увидеть:

public IEnumerable<int> GetInts()
{
    <GetInts>d__6d d__d = new <GetInts>d__6d(-2);
    d__d.<>4__this = this;
    return d__d;
}

Edit - добавление дополнительной информации о реализации GetInts:
Способ, которым эта реализация делает его ленивым, основан на Enumerator MoveNext() методе. Когда генерируется перечислимый вложенный класс (d__6d в примере), он имеет состояние и к каждому состоянию подключается значение (это простой случай, в более сложных случаях значение будет оцениваться, когда код достигнет состояния). Если мы посмотрим в код MoveNext() из d__6d, то увидим состояние:

private bool MoveNext()
{
    switch (this.<>1__state)
    {
        case 0:
            this.<>1__state = -1;
            this.<>2__current = 1;
            this.<>1__state = 1;
            return true;

        case 1:
            this.<>1__state = -1;
            this.<>2__current = 2;
            this.<>1__state = 2;
            return true;

        case 2:
            this.<>1__state = -1;
            this.<>2__current = 3;
            this.<>1__state = 3;
            return true;

        case 3:
            this.<>1__state = -1;
            break;
    }
    return false;
}

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

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

[TestFixture]
public class YieldExample
{
    private int flag = 0;
    public IEnumerable<int> GetInts()
    {
        yield return 1;
        flag = 1;
        yield return 2;
        flag = 2;
        yield return 3;
        flag = 3;
    }

    [Test]
    public void Test()
    {
        int expectedFlag = 0;
        foreach (var i in GetInts())
        {
            Assert.That(flag, Is.EqualTo(expectedFlag));
            expectedFlag++;
        }

        Assert.That(flag, Is.EqualTo(expectedFlag));
    }
}

Надеюсь, стало немного понятнее. Рекомендую посмотреть на код с помощью Reflector и понаблюдать за скомпилированным кодом при изменении кода "yield".

13
ответ дан 4 December 2019 в 08:51
поделиться

Если вы хотите узнать больше о том, что делает компилятор при использовании yield return , ознакомьтесь с этой статьей Джона Скита: Детали реализации блока итератора

7
ответ дан 4 December 2019 в 08:51
поделиться

В основном итераторы, реализованные с помощью операторов yield , компилируются в класс, реализующий конечный автомат .

Если вы никогда не foreach (= перебираете и фактически используете) возвращенный IEnumerable , код на самом деле никогда не выполняется. И если вы это сделаете, выполняется только минимальный код, необходимый для определения следующего возвращаемого значения, только для возобновления выполнения, когда будет запрошено следующее значение.

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

4
ответ дан 4 December 2019 в 08:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: