似非Iterator

C#2.0でサポートされる機能の1つにIteratorがあります。これは、CoroutineとかMicroThreadとかFiberとか呼ばれることもあります。C#2.0の紹介ではforeachが楽に書けることしか強調していないのですが、関数に状態を持たせることができ、また、呼び出し側が制御を渡さずにループ処理が出来ることが便利です。

分かり難いので具体例を出すと、例えばディレクトリ走査してパターンにマッチしたファイルをコピーする関数を作ったとします。後日、今度はマッチしたファイルを削除する関数が欲しくなったとします。ファイルコピー関数の処理部分を削除にすれば良いだけですが、ディレクトリ走査とファイルコピーがガッチリ結合してしまっているので再利用出来ません。もちろん、コールバック関数を渡して、呼び出して貰う手もあります。しかし、関数を呼び出す側は、ファイル名を貰ってコピーなり、削除なり出来ればもっと自然に書けるはずです。


Iterator(Coroutine)のサンプルとしてフィボナッチ数列を求めるという、アルゴリズムの入門書にあるような例を見てみます。
(追記) C#2.0を持っていないので、自作Coroutineでのサンプルです。C#2.0でも同様のことが出来ると思うけど、どーなんでしょ?(^^;

class MyApp : Coroutine {
  // フィボナッチ数をもとめる
  void Fibonacci() {
    int a = 0, b = 1, t;
    // 無限ループさせる
    while (true) {
      Yield(b);
      t = a;
      a = b;
      b = t + b;
    }  
  }

  protected override void Run() {
    // コルーチンで実行する関数
    Fibonacci();
  }

  static void Main(string[] args) {
    MyApp app = new MyApp();
    app.Start();

    // フィボナッチ数列の最初の要素
    app.MoveNext();
    Console.WriteLine(app.Current);
    // フィボナッチ数列の次の要素
    app.MoveNext();
    Console.WriteLine(app.Current);

    Console.WriteLine("Hit any key!");
    Console.ReadLine();

    // 更に10個
    for (int i = 0; i < 10; ++i) {
      app.MoveNext();
      Console.Write(app.Current + " ");
    }    

    Console.WriteLine("\nHit any key!");
    Console.ReadLine();

    // おまけにもう一つ
    app.MoveNext();
    Console.WriteLine(app.Current);
  }
}

ここで、面白いのはFibonacci関数自体は無限ループしていることです。Main関数を見れば分かりますが、無限ループしているはずの関数を1ステップずつ進めて、その時の状態(ループ内でYieldされた値)にアクセスしています。Iteratorが使えると、処理がかなりスッキリ書けるようになります。

C#2.0が待ち遠しいですが、現状でもIteratorもどきは作ることが出来ます。

Implementing Coroutines for .NET by Wrapping the Unmanaged Fiber API
一つはWindows固有になりますが、こちらのFiberを使う方法。ただ、非公開っぽいAPIを使っていたり、Managed C++を使わなければならなかったり、実装にバグがあったりするらしいので、使用するのは難しいです。
もう一つは、スレッドと同期イベントを使うもの。パフォーマンスは悪くても構わないから、簡単に処理を書きたい場合に有効かもしれません。

実装はこんな感じです。yaneSDK4Csから引っ張ってきたものですが(^^;

using System;
using System.Collections;
using System.Threading;

public delegate void CoroutineStart(Coroutine cor);

public class Coroutine : IEnumerable, IEnumerator, IDisposable {
  protected CoroutineStart start_proc;
  private Thread thread;
  private AutoResetEvent event1;
  private AutoResetEvent event2;
  private object result;
  private bool disposed;

  protected volatile bool isDone;

  public Coroutine() {}
  public Coroutine(CoroutineStart start) {
    start_proc = start;
  }
  ~Coroutine() {
    Dispose(false);
  }
  public void Yield(object o) {
    result = o;
    Suspend();
  }
  public void Start() {
    Reset();
  }
  private void Main() {
    event2.WaitOne();
    Run();
    isDone = true;
    event1.Set();
  }
  protected void Resume() {
    event2.Set();
    event1.WaitOne();
  }
  protected void Suspend() {
    event1.Set();
    event2.WaitOne();
  }
  protected virtual void Run() {
    if (start_proc != null)
      start_proc(this);
  }
  protected bool IsDone() {
    return isDone;
  }
  protected void Dispose(bool disposing) {
    if (!disposed) {
      if (disposing) {
        if (thread != null) {
          if (thread.IsAlive)
            thread.Abort();
          thread = null;
        }
      }
      if (event1 != null) {
        event1.Close();
        event1 = null;
      }
      if (event2 != null) {
        event2.Close();
        event2 = null;
      }
      disposed = true;
    }
  }
  public IEnumerator GetEnumerator() {
    return this;
  }
  public void Reset() {
    Dispose(true);
    event1 = new AutoResetEvent(false);
    event2 = new AutoResetEvent(false);
    thread = new Thread(new ThreadStart(Main));
    thread.IsBackground = true;
    thread.Start();
    isDone = false;
  }
  public object Current {
    get {
      return result;
    }
  }
  public bool MoveNext() {
    if (isDone)
      return false;
    Resume();
    return !isDone;
  }
  public void Dispose() {
    Dispose(true);
    GC.SuppressFinalize(this);
  }
}

一応、継承とdelegateに対応しています。