惰性评估示例 Fibonacci 数
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics; // also add reference to System.Numberics
namespace ConsoleApplication33
{
class Program
{
private static IEnumerable<BigInteger> Fibonacci()
{
BigInteger prev = 0;
BigInteger current = 1;
while (true)
{
yield return current;
var next = prev + current;
prev = current;
current = next;
}
}
static void Main()
{
// print Fibonacci numbers from 10001 to 10010
var numbers = Fibonacci().Skip(10000).Take(10).ToArray();
Console.WriteLine(string.Join(Environment.NewLine, numbers));
}
}
}
它是如何工作的(我建议在 IL Disaambler 工具中反编译生成的 .exe 文件):
- C#编译器生成一个实现
IEnumerable<BigInteger>
和IEnumerator<BigInteger>
的类(ildasm 中的<Fibonacci>d__0
)。 - 该类实现了一个状态机。状态由方法中的当前位置和局部变量的值组成。
- 最有趣的代码是
bool IEnumerator.MoveNext()
方法。基本上,MoveNext()
做什么:- 恢复当前状态。像
prev
和current
这样的变量成为了我们类的领域(ildasm 中的<current>5__2
和<prev>5__1
)。在我们的方法中,我们有两个位置(<>1__state
):首先在开口大括号,第二个在yield return
。 - 执行代码直到下一个
yield return
或yield break
/}
。 - 对于
yield return
,结果值被保存,因此Current
属性可以返回它。true
被退回。此时,为下一次MoveNext
调用再次保存当前状态。 - 对于
yield break
/}
方法,只返回false
意味着迭代完成。
- 恢复当前状态。像
另请注意,第 10001 个数字长度为 468 个字节。状态机仅将 current
和 prev
变量保存为字段。如果我们想要保存序列中从第一个到第 10000 个的所有数字,则消耗的内存大小将超过 4 兆字节。因此,如果使用得当,延迟评估可以减少某些情况下的内存占用。