收益表对计划流程的影响

时间:2013-05-29 05:52:03

标签: c# yield-return

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

为了演示使用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;
        }
    }
}

代码的作用非常明显,只需使用ComputePower将{2}增加为幂,返回IEnumerable。在调试代码时,我看到yield语句返回控制到foreach循环,value变量用最新的权力结果即更新。 2,4,8,16,32。

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

有人可以解释为什么会这样吗?是否与yield关键字有关?

4 个答案:

答案 0 :(得分:7)

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

来自msdn:

  

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

答案 1 :(得分:1)

在较高的层面上,您可以将yield视为'返回值并冻结方法的当前状态。当下一次调用生成器时,该方法将解冻并从yield'后面的行开始继续。因此,任何仅在方法开始时并且实际上不在yield存在的循环中的行只会被调用一次,它不会再次启动整个方法。

在低级别上,yield是由编译器将您的方法转换为状态机实现的,其中在方法的开头添加了跳转表并且我们采用了跳转(这产生了代码行)当你调用方法时开始执行)由生成器的最后一个'状态'决定。类似的编码技术用于等待/异步状态机,并允许在程序员下更容易隐藏很多复杂性理解模型。

答案 2 :(得分:1)

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

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

实际上,每次迭代都会看到你的方法体执行一次,每次到达Current语句时,循环都会被“中断”。

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

答案 3 :(得分:0)

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