IteratorによるUIの分離

ユーザインタフェースを持つアプリケーションを書く場合、UI部分とロジックをうまく分離できないことがあります。たとえば、コンソールから数値を入力し、その平均値を求めるようなことを考えてみます。

using System;

class Program
{
    static void Main ()
    {
        int sum = 0;
        int cnt = 0;

        Console.WriteLine ("Input number:");
        while (true) 
        {
            string input = Console.ReadLine ();
            if (input.ToUpper() == "Z")
                break;
            int n;
            if (Int32.TryParse (input, out n))
            {
                sum += n;
                ++cnt;
            }
        }

        if (cnt > 0)
            Console.WriteLine ("Average = {0}", sum / cnt);
    }
}

このコードだとロジックがコンソールに依存してしまい、たとえば、数値をファイルから読み出すような変更があった場合、大きく書き換えることになります。代案としては、ユーザが入力した値を配列に入れて、後で一括して処理するなどがあります。しかし、ユーザからの入力は件数がわからないので、動的配列を用意して保存する必要があり、メモリがもったいない気がします。こんな時には、Iteratorを使うと便利です。
先ほどのコードをIteratorを使って書き換えてみます。

using System;
using System.Collections.Generic;

class Program
{
    static IEnumerable<int> Input ()
    {
        while (true) 
        {
            string input = Console.ReadLine ();
            if (input.ToUpper() == "Z")
                yield break;
            int n;
            if (Int32.TryParse (input, out n))
                yield return n;

        }
    }

    static void Main ()
    {
        int sum = 0;
        int cnt = 0;

        Console.WriteLine ("Input number:");
        foreach (int n in Input ())
        {
            sum += n;
            ++cnt;
        }

        if (cnt > 0)
            Console.WriteLine ("Average = {0}", sum / cnt);
    }
}

こんな感じ。動的配列も不要です。Iteratorを使うと状態マシンを簡単に作れるので、とても便利ですよ〜