★C# の為のデザインパターン

Iterator パターンと foreach 文の甘い関係♥

Iterator パターンとは

Iterator パターンとは要素の集合体(配列やリストなど、複数の物が含まれている物)の中の要素を、一つ一つ取り出しながら、集合体を走査するパターンです。
このパターンを適用することにより

  1. 集合体に対して同時に複数の走査を可能にする。
  2. 集合体に走査の為のインターフェイスを含めなくてよい(複数の走査アルゴリズムのサポート、走査のアルゴリズムの変更がし易くなります)
などのメリットが得られます。

Iteratorパターンでは集合体(Aggregate)と集合体の要素を列挙するもの(Iterator)が登場します。

Iterator パターンと foreach 文

C# には コレクション系オブジェクトの中身を順番に処理していくための構文として foreach 文があります。
既成の言語、たとえば Java での Iterator の基本的な使用法は以下のような感じです。

Iterator iterator = someAggreegate.iterator();

iterator.First();
while ( !iterator.IsDone())
{
    object element = iterator.CurrentItem();
    System.out.println (" value:" + element.someValue);
    iterator.Next();
}
あるいは
Iterator iterator = someAggreegate.iterator();

for ( iterator.First(); !iterator.IsDone(); iterator.Next())
{
    object element = iterator.CurrentItem();
    System.out.println ( " value:" + element.someValue);
}
の様に for 文で処理すると美しいです。
しかし C# の foreach 文はこれよりももっと簡素に美しく処理が書けます。
C# でコレクションの要素を走査するには以下のように書きます。
foreach ( Object o in someCollection)
{
    System.Console.WriteLine ( " value:{0}", o.ToString());
}
Java のコードと比べると Iterator を操作する部分が完全に無くなって簡素になっています。
「しかしそのコレクションってのと Iterator パターンが一体何の関係があるんだ!?」と思われた方もいるかもしれません。が、この例に出てくる someCollection とは Iterator パターンの ConcreteAggregate です。
そして Iterator は foreach 文に隠されています。 Iterator が隠されてしまうのはいい事ばかりではありません*1が、そのおかげでよりいっそう簡素に書けるようになっています。( C# が Iterator パターンを文法レベルでサポートしていると解釈できます*2)
*1Iterator が直接操作できないと、複数の走査アルゴリズムの実装、条件付の走査等の柔軟な処理ができなくなります。この様な場合は C# でも C由来の構文を使用するしかなさそうです。
*2クラスライブラリがパターンを取り入れているのは珍しくありませんが、言語の文法がパターンを取り入れているというのはまだあまりありません( GoF 本によると CLU という言語などはサポートしているらしいです)

コレクション系オブジェクト作成の作成

では、foreach 文で回せるコレクションを作ってみましょう。.NET のライブラリには IEnumerable インターフェイス (Aggregate)と IEnumerator インターフェイス (Iterator)が用意されています。

.NET ライブラリのインターフェイスと Iterator パターンでの名称
GoF .NET
Aggregate クラス System.Collections.IEnumerable インターフェイス
Iterator クラス System.Collections.IEnumerator インターフェイス
これらのインターフェイスを実装して ConcreteAggregate と ConcreteIterator を作ることによって、foreach 文で使用可能なコレクションを作ることができます。
public interface IEnumerable
{
    IEnumerator GetEnumerator();
}
public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Reset();
}
それではこれらを実装するサンプルを作ってみましょう。
using System;
using System.Collections;

// プログラムのエントリーポイントを提供するクラスです。
public class TestApp
{
    public static void Main()
    {
        TestAggregate testAggregate = new TestAggregate();

        Console.WriteLine ("*** foreach を開始します。 ***");
        foreach (TestElement elem in testAggregate)
        {
            Console.Write ("{0} ", elem.nNumber);
        }
        Console.WriteLine ("*** foreach を終了します。 ***");
    }
}
public class TestElement
{
    private int m_nNumber;
    public int nNumber
    {
        get
        {
            return m_nNumber;
        }
    }
    public TestElement ( int nNumber)
    {
        m_nNumber = nNumber;
    }
}
// コレクションクラスです。
public class TestAggregate : IEnumerable
{
    public TestElement[] testElement;

    public TestAggregate()
    {
        int nCnt;

        testElement = new TestElement[3];
        for ( nCnt = 0; nCnt < testElement.Length; nCnt++)
        {
            testElement[nCnt] = new TestElement ( nCnt);
        }
    }

    public IEnumerator GetEnumerator()
    {
        return new TestEnumerator ( this);
    }

    // Iterator の役割をするクラスです。( コレクションの内部クラスになっています。)
    private class TestEnumerator : IEnumerator
    {
        private TestAggregate m_parent;
        private int m_nCnt = 0;

        public TestEnumerator ( TestAggregate ta)
        {
            Console.Write ("Iterator のコンストラクタです。\n");
            m_parent = ta;
            this.Reset();
            Console.Write ("直前の Reset() は Iterator が呼び出しています( foreach 文による呼び出しではありません。)\n");
        }

        public bool MoveNext()
        {
            Console.Write ("MoveNext() です。次の要素に移動します。\n");
            m_nCnt++;
            if ( m_nCnt < m_parent.testElement.Length)
            {
                // まだ残りの要素がある場合は true を返す
                return true;
            }
            else
            {
            // 全ての要素を列挙し終わったら false を返す
                return false;
            }
        }

        public object Current
        {
            get
            {
                Console.Write ("プロパティ Current の取得です。現在の要素を返します\n");
                return m_parent.testElement[m_nCnt];
            }
        }

        public void Reset()
        {
            Console.Write ("Reset() です。初期化します\n");
            m_nCnt = -1;
        }
    }
}
以下はこのコードの実行結果です。
*** foreach を開始します。 ***
Iterator のコンストラクタです。
Reset() です。初期化します
直前の Reset() は Iterator が呼び出しています( foreach 文による呼び出しではありません。)
MoveNext() です。次の要素に移動します。
プロパティ Current の取得です。現在の要素を返します
0 MoveNext() です。次の要素に移動します。
プロパティ Current の取得です。現在の要素を返します
1 MoveNext() です。次の要素に移動します。
プロパティ Current の取得です。現在の要素を返します
2 MoveNext() です。次の要素に移動します。
*** foreach を終了します。 ***
これを見るといくつか注意すべき点があるのがわかります。
foreach 文は IEnumerator.Reset() を呼び出さないようです。そして、要素を取り出す前に IEnumerator.MoveNext() が呼び出されるため Iterator が生成された時点では、Aggregate の最初の要素の「一つ前」を指すようにしなければなりません。
今回は Iterator のコンストラクタで Reset() を呼び出し、そこで処理しています。

結論

C# で Iterator パターンを適用するときは IEnumerable と IEnumerator を実装する形にすると foreach 文でのお気楽操作が可能になります。


[ 戻る | トップページ ]
Looking' for (c)2000-2007 Takel All right reserved.