Yield是如何实现延迟加载模式的?

发布于 2024-08-30 18:14:34 字数 51 浏览 8 评论 0原文

yield是如何实现延迟加载模式的?

How yield implements the pattern of lazy loading?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

迷离° 2024-09-06 18:14:34

在需要时,yield 实现不会到达代码。

例如,以下代码:

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

实际上会编译为实现 IEnumerable的嵌套类,并且 GetInts() 的主体将返回该类的实例。

使用反射器,您可以看到:

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

编辑 - 添加有关 GetInts 实现的更多信息:
此实现使其变得懒惰的方式基于 Enumerator MoveNext() 方法。当生成可枚举嵌套类时(示例中的 d__6d),它具有一个状态,并且每个状态都连接一个值(这是一个简单的情况,在更高级的情况下,该值当代码达到该状态时将被评估)。如果我们查看 d__6dMoveNext() 代码,我们将看到以下状态:

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”代码时观察编译后的代码。

yield implementation doesn't reach the code until it's needed.

For example, this code:

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

Will actually compile into a nested class which implements IEnumerable<int> and the body of GetInts() will return an instance of that class.

Using reflector you can see:

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

Edit - adding more info about GetInts implementation:
The way this implementation makes it lazy is based on the Enumerator MoveNext() method. When the enumerable nested class is generated (<GetInts>d__6d in the example), it has a state and to each state a value is connected (this is a simple case, in more advanced cases the value will be evaluated when the code reach the state). If we take a look in the MoveNext() code of <GetInts>d__6d we'll see the state:

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;
}

When the enumerator is asked for the current object it returns the object which is connected to the current state.

In order to show that the code is evaluated only when it's required you can look at this example:

[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));
    }
}

I hope it's a bit more clear. I recommend to take a look at the code with Reflector and observe the compiled code as you change the "yield" code.

清旖 2024-09-06 18:14:34

如果您想了解有关编译器在使用 yield return 时执行的操作的更多信息,请查看 Jon Skeet 撰写的这篇文章:迭代器块实现细节

If you want to know more about what the compiler is doing when using yield return, check this article by Jon Skeet: Iterator block implementation details

骷髅 2024-09-06 18:14:34

基本上,使用 yield 语句实现的迭代器被编译成一个实现 状态机的类

如果您从未foreach(=迭代并实际使用)返回的IEnumerable,则该代码永远不会实际执行。如果这样做,则仅执行确定要返回的下一个值所需的最少代码,仅在请求下一个值时恢复执行。

当您在调试器中单步执行此类代码时,您实际上可以看到这种行为发生。至少尝试一次:我认为看到这一步步发生是很有启发的。

Basically iterators implementated by using yield statements are compiled into a class that implements a state machine.

If you never foreach (=iterate over and actually use) the returned IEnumerable<T>, the code is never actually executed. And if you do, only the minimal code needed to determine the next value to return is executed, only to resume execution when a next value is requested.

You can actually see this behavior happening when you single step such code in the debugger. Try it at least once: I think it's illuminating to see this happen step by step.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文