C# 收益表实现

C# 收益表实现,c#,.net,yield,iterator,C#,.net,Yield,Iterator,我想以易于理解的形式了解yield语句的所有内容 我已经阅读了yield语句,以及它在实现迭代器模式时的易用性。然而,大部分是非常干燥的。我想了解一下微软是如何处理收益率的 另外,什么时候使用yield break?yield通过在内部构建状态机来工作。当例程退出并下次从该状态恢复时,它存储例程的当前状态 您可以使用Reflector查看编译器如何实现它 当您想要停止返回结果时,将使用产量中断。如果没有屈服中断,编译器将在函数末尾假设一个屈服中断(就像正常函数中的返回;语句)正如Mehrdad所

我想以易于理解的形式了解
yield
语句的所有内容

我已经阅读了
yield
语句,以及它在实现迭代器模式时的易用性。然而,大部分是非常干燥的。我想了解一下微软是如何处理收益率的


另外,什么时候使用yield break?

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

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


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

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


以及使用反射器(另一个很好的建议),你可能会发现有用的。如果不是
finally
块,它将相对简单,但它们引入了一个额外的复杂度维度

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

实际上,这并不完全像是使用一个内置的实现,该实现将在后台使用,而是编译器通过实现一个相关接口(包含
yield
关键字的方法的返回类型)将
yield
相关代码重写到状态机

(有限)只是一段代码,它根据您在代码中的位置(取决于之前的状态,输入)转到另一个状态操作,当您使用方法返回类型为
IEnumerator
/
IEnumerator
时,几乎就是这样。
yield
关键字将创建另一个操作以从上一个操作移动到下一个状态,因此在
MoveNext()
实现中创建状态管理

这正是C#编译器/Roslyn要做的事情:检查
yield
关键字的存在以及包含方法的返回类型,无论它是
IEnumerator
IEnumerable
IEnumerable
还是
IEnumerable
,然后创建反映该方法的私有类,整合必要的变量和状态

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

琐事1:在编写
异步
/
等待
代码时使用的(代码)也继承自
状态机编写器
,因为它还利用了后面的状态机

如前所述,状态机在
bool MoveNext()
生成的实现中得到了很好的反映,其中有一个
开关
+有时一些老式的
转到
,它基于状态字段,表示方法中不同状态的不同执行路径

编译器从用户代码生成的代码看起来不太“好”,主要是因为编译器在这里和那里添加了一些奇怪的前缀和后缀

例如,代码:

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...
正如您所看到的,这个实现远非简单明了,但它确实做到了

琐事2:使用
IEnumerable
/
IEnumerable
方法返回类型会发生什么情况?
好的,它不只是生成一个实现
IEnumerator
的类,而是生成一个同时实现
IEnumerable
IEnumerator
的类,这样
IEnumerator 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();
}
公共接口IEnumerable:IEnumerable
{
新的IEnumerator GetEnumerator();
}
公共接口IEnumerator:IDisposable,IEnumerator
{
T当前{get;}
}
公共接口IEnumerator
{
bool-MoveNext();
当前对象{get;}
无效重置();
}
您还可以使用不同的路径/分支以及编译器重写的完整实现进行检查

这是用创建的,您可以使用该工具尝试不同的
产生
相关执行路径,并查看编译器将如何在
MoveNext
实现中将它们重写为状态机

关于问题的第二部分,即,
收益率突破
,已经回答了

它指定迭代器已经结束 将break作为不返回值的返回语句生成


“例程的当前状态”是什么意思:处理器寄存器值、帧指针等?请看@Tcraft Microsoft的规范化实现没有使用不同的堆栈/分段堆栈等。它们使用堆分配的对象来存储状态。
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";
        }
    }
} 
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;
} 
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();
}