Yield语句对程序流的影响
本文关键字:影响 程序 语句 Yield | 更新日期: 2023-09-27 18:16:02
我试图理解在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
变量更新为power ie的最新结果。2、4、8、16、32.
不完全理解yield
的使用,我期望ComputePower
在值迭代ComputePower
时被调用多次,并且我将看到"Arguments to ComputePower are "
等控制台写入发生5次。实际发生的情况是,ComputePower
方法似乎只被调用一次。我每次运行只看到一次"Arguments to ComputePower.."
字符串。
yield
关键字有关?
foreach将迭代从ComputePower返回的IEnumerable。"Yield return"会自动创建IEnumerable的实现,因此您不必手动滚动它。如果你在while循环中设置一个断点,你会看到它在每次迭代中都被调用
从msdn:通过使用foreach语句或LINQ查询来消费迭代器方法。foreach循环的每次迭代都会调用迭代器方法。当在迭代器方法中到达yield return语句时,返回表达式,并保留代码中的当前位置。下次调用迭代器函数时,从该位置重新开始执行。
yield return
导致编译器构建一个状态机,该状态机使用你的方法体实现IEnumerable<T>
。它从你的方法返回一个对象,而没有实际调用你编写的方法体——编译器已经用更复杂的东西取代了它。
当您在状态机生成的IEnumerator<T>
上调用MoveNext()
时(例如在foreach
循环期间),状态机将执行您的方法代码,直到到达第一个yield return
语句。然后,它将Current
的值设置为您返回的任何值,然后将控制权交还给调用者。
在实践中,看起来好像你的方法体每次迭代执行一次,并且每次到达yield return
语句时循环被"中断"。
如果你在方法的while循环中设置了一个断点,你会看到堆栈中包含了一个对编译器生成的类型MoveNext()
的调用,而你的方法的主体已经成为该类型的一部分。
在高层次上,您可以将yield
视为"返回一个值并冻结方法的当前状态"。当下次调用生成器时,该方法将解冻并从yield'后面的行开始恢复。因此,任何只在方法开头而不在yield
存在的循环中的行只会被调用一次,它不会重新启动整个方法。
在较低的层次上,yield
是由编译器实现的,将你的方法转换成一个状态机,在方法开始时添加一个跳转表,我们采取的跳转(当你调用方法时,我们开始执行的代码行)是由生成器最后进入的"状态"决定的。类似的编码技术用于await/async状态机,并允许在更容易理解的模型下对程序员隐藏许多复杂性。
yield操作符将强制编译器创建一个自定义类来实现您的逻辑。更好的理解它的方法是反编译结果exe并观察它