EnumerableEx.Generateを使う際の注意点


Linq to Objectの幅を広げてくれる、System.Interactiveの中に、指定したルールと終了条件によって、シーケンスを生成してくれるEnumerableEx.Generateメソッドというものがあります。
このメソッドは便利なのですが、比較的あり得るシナリオとして、数値のシーケンスを返すようなものを生成する場合、状況によって無限ループに陥ってしまうことがあります。

無限ループに陥ってしまう例

たとえば、0~20までの整数シーケンスを生成したい場合、以下のように書いたとしましょう。

iteration.cs
using System;
using System.Linq;

namespace Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var iteration = EnumerableEx.Generate(0, i => i < 20, i => i++, i => i);

            iteration.ForEach(Console.WriteLine);
        }
    }
}

この場合、意図と反して延々0が出力され続けます。

解決方法

先にこのような場合、意図したとおりに動かすにはどのように修正すれば良いのか以下に示します。

Modify.cs
using System;
using System.Linq;

namespace Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var iteration = EnumerableEx.Generate(0, i => i < 20, i => ++i, i => i);

            iteration.ForEach(Console.WriteLine);
        }
    }
}

変更点として、Generateメソッドの"iterate"引数に与えているFuncを、後置きインクリメントから、前置きインクリメントに変更しているだけです。

なぜこのようなことが起きてしまうのか

先に書いた後置きインクリメントがなぜ無限ループに陥ってしまうかは、λでは無く、メソッドにしてしまうととても簡単に理由がわかります。

IterationAnatomy.cs

using System;
using System.Linq;

namespace Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var iteration = EnumerableEx.Generate(0, i => i < 20, Iteration, i => i);

            iteration.ForEach(Console.WriteLine);
        }

        static int Iteration(int i)
        {
            return i++;
        }
    }
}

上記の例では、iterate引数のデリゲートをλでは無く、実際のメソッドとして書き起こしました。
さて、Iterationメソッドはごく単純に引数iを、後置インクリメントして結果を返すだけです。これは結局iで受けた数値をそのまま返しているだけに過ぎないコトになります。
従って、Generateメソッドにこのようなiterateを適用してしまうと、永遠に値が変わらず、従って条件に合致し続ける初期値だけを返す無限のシーケンスを生成してしまいます。
for文では後置きでも前置きでも同じように評価してくれますが、この場合は後置きと前置きで意味が全く変わってくるので注意が必要でしょう。