屈服声明实施

7 .net c# iterator yield

我希望以一种易于理解的形式了解该声明的所有内容yield.

我已经阅读了关于yield实现迭代器模式时的语句及其简易性.但是,大部分都非常干燥.我想深入研究微软如何处理收益率.

另外,你什么时候使用收益率突破?

Meh*_*ari 11

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

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

yield break当您想要停止返回结果时使用.如果你没有a yield break,编译器会在函数末尾假定一个(就像return;普通函数中的语句一样)


Jon*_*eet 9

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

除了使用Reflector(另一个很好的建议),您可能会发现我关于迭代器块实现的文章很有用.如果它不是用于块,那将是相对简单的finally- 但它们引入了一个额外的复杂性维度!


Eho*_*ret 5

让我们稍微回顾一下:yield关键字被翻译成许多其他人对状态机所说的。

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

(有限)状态机只是一段代码,它取决于您在代码中的位置(取决于之前的状态、输入)转到另一个状态操作,这几乎就是您使用和产生时发生的情况方法返回类型为IEnumerator<T>/ IEnumerator。该yield关键字就是要创建另一个行动,从以前的一个,因此状态管理是创建移动到下一个状态MoveNext()的实现。

这正是 C# 编译器 / Roslyn 要做的:检查yield关键字的存在以及包含方法的返回类型的类型,无论是 a IEnumerator<T>, IEnumerable<T>, IEnumeratororIEnumerable然后创建一个反映该方法的私有类,集成必要的变量和状态。

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

Trivia 1 : AsyncRewriter(在您编写async/await代码时使用的也继承自,StateMachineRewriter因为它也利用了背后的状态机。

如前所述,状态机在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...
Run Code Online (Sandbox Code Playgroud)

编译后与上面那段代码相关的变量和类型将如下所示:

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;
Run Code Online (Sandbox Code Playgroud)

关于状态机本身,让我们看一个非常简单的例子,它带有一个虚拟分支,用于产生一些偶数/奇数的东西。

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";
        }
    }
} 
Run Code Online (Sandbox Code Playgroud)

将被翻译MoveNext为:

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;
} 
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,这个实现远非简单,但它完成了工作!

琐事 2IEnumerable/IEnumerable<T>方法返回类型会发生什么?
好了的,而不是只生成实现类IEnumerator<T>,它将,生成一个类,同时实现IEnumerable<T>还有IEnumerator<T>,这样的实施IEnumerator<T> 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();
}
Run Code Online (Sandbox Code Playgroud)

您还可以使用不同的路径/分支以及编译器重写的完整实现来查看此示例

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

关于问题的第二部分,即,yield break已在此处回答

它指定迭代器已经结束。您可以将 yield break 视为不返回值的 return 语句。