惰性评估示例 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 文件):

  1. C#编译器生成一个实现 IEnumerable<BigInteger>IEnumerator<BigInteger> 的类(ildasm 中的 <Fibonacci>d__0)。
  2. 该类实现了一个状态机。状态由方法中的当前位置和局部变量的值组成。
  3. 最有趣的代码是 bool IEnumerator.MoveNext() 方法。基本上,MoveNext() 做什么:
    • 恢复当前状态。像 prevcurrent 这样的变量成为了我们类的领域(ildasm 中的 <current>5__2<prev>5__1)。在我们的方法中,我们有两个位置(<>1__state):首先在开口大括号,第二个在 yield return
    • 执行代码直到下一个 yield returnyield break / }
    • 对于 yield return,结果值被保存,因此 Current 属性可以返回它。true 被退回。此时,为下一次 MoveNext 调用再次保存当前状态。
    • 对于 yield break / } 方法,只返回 false 意味着迭代完成。

另请注意,第 10001 个数字长度为 468 个字节。状态机仅将 currentprev 变量保存为字段。如果我们想要保存序列中从第一个到第 10000 个的所有数字,则消耗的内存大小将超过 4 兆字节。因此,如果使用得当,延迟评估可以减少某些情况下的内存占用。