9

我想以易于理解的形式了解有关该声明的所有信息。yield

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

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

4

3 回答 3

14

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

您可以使用 Reflector 查看编译器是如何实现的。

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

于 2009-04-12T21:53:38.630 回答
11

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

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

于 2009-04-12T21:56:01.170 回答
6

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

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

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

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

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

琐事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...

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

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为:

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

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

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

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

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

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

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

于 2019-01-12T19:46:56.143 回答