外部イテレータもできた!

前回 のForEachDetailを、外部イテレータからも利用できるためには、外部イテレータが「現在の値」を取得できるようにしないとダメ。2分木のイテレータに関して再開 - チキン煮込みチーズミックス4辛 からは内部イテレータだけに話を絞っていたので、ForEachDetailに渡す処理をActionにしていたけど、もともと 内部イテレータを外部イテレータに変形する(その1:配列の場合)再考 - チキン煮込みチーズミックス4辛 でやってたみたいに、IProcedureにしてやれば外部イテレータ側で「最後に走査された値」を取得できるようになる。
あとは、これらを踏まえて

  • IProcessのメソッドProcess
  • IProcessWithMemoryのメソッドLastProcessedValue

が確実に交互に呼び出されるようにするといい。
一方で、内部イテレータの方は外部イテレータほど条件がキツくなく、(それが好ましいかはおいといて)IProcedureのProcessが全要素に対して適用されさえすれば問題ない。

ソースコードは以下。
TreeNodeの該当箇所のみ

/// <summary>内部イテレータ</summary>
public void ForEach(Action<T> action)
{
  JobHolder holder = new JobHolder();
  IProcedure<T> proc = new Procedure<T>(action);
  ForEachDetail(this, proc, holder, null);
  while (null != holder.Job)
  {
    holder.Job();
  }
}

/// <summary>外部イテレータを取得</summary>
public IEnumerator<T> GetEnumerator()
{
  JobHolder holder = new JobHolder();
  IProcedureWithMemory<T> proc =
    new ProcedureWithMemory<T>();

  Action init = () => ForEachDetail(this, proc, holder, null);
  holder.Job = init;

  return new OutIterator<T>(holder, proc, init);
}

/// <summary>外部イテレータを取得</summary>
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{
  return GetEnumerator();
}

/// <summary>委譲される手続きの入口</summary>
static void ForEachDetail(
  TreeNode<T> root,
  IProcedure<T> proc,
  JobHolder holder,
  Action cont)
{
  // rootを根とする部分木の、最小のノードを見つける
  TreeNode<T> leftmost = root;
  while (leftmost._left != null) leftmost = leftmost._left;

  // 見つけたノードから走査開始
  // contは、この部分木の走査が終了したあとにすべきこと
  //   例)この部分木を包むひとつ上の部分木の走査を開始する
  AtLeftMost(leftmost, proc, holder, cont);
}

/// <summary>各部分木の最左のノードを処理</summary>
static void AtLeftMost(
  TreeNode<T> leftmost,
  IProcedure<T> proc,
  JobHolder holder,
  Action cont)
{
  proc.Process(leftmost._value);

  if (leftmost._parent != null)
  {
    if (leftmost == leftmost._parent._left)
    {
      holder.Job =
        () => OnTheWay(leftmost._parent, proc, holder);
    }
    else
    {
      holder.Job = cont;
    }
  }
  else
  {
    holder.Job = null;
  }
}

/// <summary>部分木のルートに至るまでの途中</summary>
static void OnTheWay(TreeNode<T> node, IProcedure<T> proc, JobHolder holder)
{
  proc.Process(node._value);

  if (node._right != null)
  {
    TreeNode<T> nextRoot = GetNextRoot(node);
    if (nextRoot == null)
    {
      // 出口はここしかない?
      // ForEachでForEachDetailに渡したnullは使われないな...
      holder.Job =
        () => ForEachDetail(node._right, proc, holder, null);
    }
    else
    {
      holder.Job =
        () => ForEachDetail(
          node._right,
          proc, holder,
          () => OnTheWay(nextRoot, proc, holder));
    }
  }
  else
  {
    if (node._parent != null)
    {
      holder.Job =
        () => OnTheWay(node._parent, proc, holder);
    }
    else
    {
      holder.Job = null;
    }
  }
}

/// <summary>次の部分木のルートを見つける</summary>
static TreeNode<T> GetNextRoot(TreeNode<T> node)
{
  while (node._parent != null)
  {
    if (node._parent._left == node) return node._parent;
    node = node._parent;
  }
  return null;
}

外部イテレータのクラス。無駄にGenericにしてみた。

class OutIterator<T> : IEnumerator<T>
{
  JobHolder _holder;

  IProcedureWithMemory<T> _proc;

  Action _initCont;

  public OutIterator(JobHolder holder,
    IProcedureWithMemory<T> proc,
    Action initCont)
  {
    _holder = holder;
    _proc = proc;
    _initCont = initCont;
    Reset();
  }

  public T Current
  {
    get
    {
      CheckDisposed();
      return _proc.LastProcessedValue();
    }
  }

  public void Dispose()
  {
    CheckDisposed();
    _holder = null;
    _initCont = null;
    _proc = null;
  }

  object System.Collections.IEnumerator.Current
  {
    get { return Current; }
  }

  public bool MoveNext()
  {
    CheckDisposed();

    if (_holder.Job == null) return false;
    _holder.Job();
    return true;
  }

  public void Reset()
  {
    CheckDisposed();
    _holder.Job = _initCont;
  }

  void CheckDisposed()
  {
    if (_holder == null ||
      _proc == null)
    {
      throw new InvalidOperationException();
    }
  }
}

IProcedure、Procedure、IProcedureWithMemory、ProcedureWithMemoryは 内部イテレータを外部イテレータに変形する(その1:配列の場合)再考 - チキン煮込みチーズミックス4辛 を、JobHolderは 2分木のイテレータに関して再開 - チキン煮込みチーズミックス4辛 を参照。もちろんTreeクラスもIEnumerableにすること。
実際に動かすと → http://ideone.com/jrUG1

という事で、一連のイテレータの件は頭の体操になったけど、メリットは...ないな。こんなアホみたいなコード書かれた日にゃぁ、ぶち切れますね。