2

我试图了解在 C# 中使用 yield 关键字,因为我正在使用的队列建模包广泛使用它。

为了演示产量的使用,我正在使用以下代码:

using System;
using System.Collections.Generic;
public class YieldTest
{
    static void Main()
    {
        foreach (int value in ComputePower(2, 5))
        {
            Console.Write(value);
            Console.Write(" ");
        }
        Console.WriteLine();
    }
    /**
     * Returns an IEnumerable iterator of ints
     * suitable for use in a foreach statement
     */
    public static IEnumerable<int> ComputePower(int number, int exponent)
    {
        Console.Write ("Arguments to ComputePower are number: " + number + " exponent: " + exponent + "\n");
        int exponentNum = 0;
        int numberResult = 1;
        while (exponentNum < exponent)
        {
            numberResult *= number;
            exponentNum++;
            // yield: 
            // a) returns back to the calling function (foreach),
            // b) updates iterator value (2,4,8,16,32 etc.)
            yield return numberResult;
        }
    }
}

代码的作用非常明显,它只是将 2 提高到一个幂,使用ComputePower它返回一个IEnumerable. 在调试代码时,我看到yield语句将控制权返回给foreach循环,并且value变量被更新为幂的最新结果,即。2、4、8、16、32。

不完全理解 的用法yield,我希望ComputePower在值迭代时被调用多次,ComputePower并且我会看到"Arguments to ComputePower are "等控制台写入发生 5 次。实际发生的情况是该方法似乎ComputePower只被调用一次。"Arguments to ComputePower.."我每次运行只看到一次字符串。

有人可以解释为什么会这样吗?跟yield关键字有关系吗?

4

4 回答 4

7

foreach 将迭代从 ComputePower 返回的 IEnumerable。“收益回报”自动创建 IEnumerable 的实现,因此您不必手动滚动它。如果您在“while”循环中放置一个断点,您将看到每次迭代都会调用它

来自 msdn:

您可以通过使用 foreach 语句或 LINQ 查询来使用迭代器方法。foreach 循环的每次迭代都会调用迭代器方法。在迭代器方法中到达 yield return 语句时,返回表达式,并保留代码中的当前位置。下次调用迭代器函数时,将从该位置重新开始执行。

于 2013-05-29T05:57:36.190 回答
2

yield return导致编译器构建一个状态机,该状态机IEnumerable<T>使用您的方法体来实现。它从您的方法返回一个对象,而不会在您编写它时实际调用方法的主体 - 编译器已将其替换为更复杂的东西。

当您调用MoveNext()IEnumerator<T>状态机生成的(例如在foreach循环期间)时,状态机将执行您的方法代码,直到它到达第一yield return条语句。然后它将值设置为Current您返回的任何值,然后将控制权交还给调用者。

实际上,看起来您的方法体每次迭代都执行一次,并且每次到达yield return语句时循环都会“中断”。

如果您在方法的 while 循环中放置断点,您将看到堆栈包含MoveNext()对编译器生成的类型的调用,您的方法的主体已成为该类型的一部分。

于 2013-05-29T06:08:31.430 回答
1

在高层次上,您可以认为是yield“返回一个值并冻结方法的当前状态。下次调用生成器时,该方法将解冻并从 yield' 之后的行开始恢复。因此,任何仅位于方法开头而不实际yield存在于循环中的行只会被调用一次,它不会再次启动整个方法。

在低层次上,yield由编译器将您的方法转换为状态机来实现,其中在方法的开头添加一个跳转表以及我们采用哪个跳转(当您调用该方法时,我们开始执行哪一行代码) 由生成器最后进入的“状态”决定。类似的编码技术用于等待/异步状态机,并允许在更易于理解的模型下对程序员隐藏很多复杂性。

于 2013-05-29T06:03:05.980 回答
0

yield 运算符将强制编译器创建一个自定义类来实现您的逻辑。理解它的更好方法是反编译结果 exe 并观察它。

于 2013-05-29T06:00:26.467 回答