代码之家  ›  专栏  ›  技术社区  ›  Sasha

收益表实现

  •  7
  • Sasha  · 技术社区  · 15 年前

    我想知道 一切 关于 yield 以易于理解的形式陈述。

    我读过关于 产量 语句及其在实现迭代器模式时的易用性。然而,大部分是非常干燥的。我想了解一下微软是如何处理收益率的。

    另外,什么时候使用收益率折让?

    4 回复  |  直到 5 年前
        1
  •  11
  •   Mehrdad Afshari    15 年前

    yield 通过在内部构建状态机来工作。它存储例程退出时的当前状态,并在下次从该状态恢复。

    您可以使用reflector查看编译器如何实现它。

    yield break 当您要停止返回结果时使用。如果你没有 产量突破 ,编译器将在函数末尾假设一个(就像 return; 正常函数中的语句)

        3
  •  7
  •   Jon Skeet    15 年前

    正如mehrdad所说,它构建了一个状态机。

    以及使用反射器(另一个很好的建议),你可能会发现 my article on iterator block implementation 有用的。它将会是 相当地 如果不是因为 finally 块-但是它们引入了一个额外的复杂度维度!

        4
  •  2
  •   Natalie Perret    5 年前

    让我们倒带一点: yield 关键字被翻译成许多其他人说的状态机。

    实际上,这与使用将在后台使用的内置实现不同,而是编译器重写 产量 通过实现一个相关接口(包含 产量 关键词)。

    A(有限) state machine 只是一段代码,它取决于您在代码中的位置(取决于前一个状态,输入)转到另一个状态操作,当您使用并屈服于方法返回类型为 IEnumerator<T> / IEnumerator . 这个 产量 关键字是将创建另一个操作以从上一个操作移到下一个状态的内容,因此状态管理是在 MoveNext() 实施。

    这正是c编译器/roslyn要做的:检查 产量 关键字加上包含方法的返回类型,不管它是 IEnumerator<t> , IEnumerable<T> , 迭代器 IEnumerable 然后创建一个反映该方法的私有类,集成必要的变量和状态。

    如果您对状态机和编译器如何重写迭代的细节感兴趣,可以在github上查看这些链接:

    琐事1 : AsyncRewriter (当你写作时使用 async / await 代码也继承自 状态机写入程序 因为它还利用了后面的状态机。

    如前所述,状态机在 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 AS:

    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<t> 方法返回类型?
    好吧,与其只是生成一个实现 IEnumerator<t> ,它将生成一个实现 IEnumerable<t> 以及 IEnumerator<t> 从而实现 IEnumerator<T> GetEnumerator() 将利用相同生成的类。

    当使用 产量 关键词:

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

    你也可以退房 this example 具有不同的路径/分支和编译器重写的完整实现。

    这是用 SharpLab ,您可以使用该工具尝试不同的 产量 相关的执行路径,并查看编译器如何将它们重写为 移到下一行 实施。

    关于问题的第二部分, yield break ,它已被回答 here

    它指定迭代器已结束。你可以想到 yield break作为不返回值的返回语句。