読者です 読者をやめる 読者になる 読者になる
ようこそ。睡眠不足なプログラマのチラ裏です。

yield(いーるど)は、つまるところコルーチンなんだよね

プログラミング C#2.0 C#3.0

今更感は否めないが、yield(いーるど)についてちょっとしたおさらいを。


yield(いーるど)はコルーチンである

ご存知のとおり、yieldはC#2.0から主にイテレータの実装の簡素化を目的として導入された便利機能。
このyield キーワードは単体では意味を成さない。yield returnあるいはyield break とすることで初めて機能する。*1
このyieldステートメント記法およびその動作は、手続き型の実装に慣れたプログラマにとっては
少々異質に感じるかもしれない。なぜ異質に感じるだろうか?それはyieldがコルーチンとして振舞うからに他ならない。

■コルーチン

コルーチン(co-routine)とはプログラミングの構造の一種。サブルーチンがエントリーからリターンまでを一つの処理単位とするのに対し、コルーチンはいったん処理を中断した後、続きから処理を再開できる。接頭辞co-は協調を意味するが、複数のコルーチンが中断/継続により協調動作を行うことによる。
サブルーチンと異なり、状態管理を意識せずに行えるため、協調的処理、イテレータ、無限リスト、パイプなど、継続状況を持つプログラムが容易に記述できる。
コルーチンはサブルーチンを一般化したものと考えられる。コルーチンをサポートする言語にはModula-2、Simula、Icon、LuaC#がある。マルチスレッドで原理的には同じことができるため、現在はそちらが使われるケースが多い。これはマルチスレッドであれば直接OSの支援を受けられることや、エントリー/リターンの構造を変えずにコードを多重化できるので、過去の言語との親和性が良いなどが理由である。
ただし、マルチスレッドの場合プログラマが同期制御を行わなければならないので、コルーチンのような簡易さはない。


コルーチン - Wikipedia

yieldを日本語に訳すと「放棄する」とか「譲渡する」とか、そんな意味。
つまり、呼び出し元に処理を戻しますよー的に読み取ることができるかもしれない。
簡単に言うとレジューム機能を有したルーチンがコルーチンというわけ。
厳密には違うっぽいけど、似たような動き(並列処理のような動き)をするので、
コルーチンのことを、マイクロスレッドあるいはファイバーなんて呼ぶこともあるみたい。*2


yieldを利用した状態遷移モデル

元ネタC# の yield return の使い道 - u_1rohのカタチという記事で、
yieldを使って状態遷移モデルを表現するという例が示されていました。おー、なるほどね。
というわけで、雑なサンプルコードに起こしてみました。

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        #region メンバ
        private IEnumerator<EState> em;
        private Point p1;
        private Point p2;
        #endregion

        #region 列挙型

        /// <summary>入力待ち状態</summary>
        public enum EState
        {
            /// <summary>始点待ち</summary>
            StartPoint
            /// <summary>終点待ち</summary>
           ,EndPoint
        }
        #endregion

        #region コンストラクタ
        public Form1()
        {
            InitializeComponent();
            em = CreateLine().GetEnumerator();
            em.MoveNext();
        }
        #endregion

        /// <summary>
        /// マウスクリックされた2点について直線を書く
        /// </summary>
        /// <returns>入力待ち状態</returns>
        public IEnumerable<EState> CreateLine()
        {
            while (true) {
                yield return EState.StartPoint;
                yield return EState.EndPoint;
                var g = this.CreateGraphics();
                g.DrawLine(new Pen(Color.Red), p1, p2);
            }
        }

        private void Mouse_Click(object sender, MouseEventArgs e)
        {
            switch ((EState)em.Current){
                case EState.StartPoint:
                    p1 = e.Location;
                    break;
                case EState.EndPoint:
                    p2 = e.Location;
                    break;
            }
            em.MoveNext();
        }
    }
}

yieldって、コルーチンとして振舞うわけだから、
通常のイテレータとしてだけじゃなくって、こーゆー風にも使えるね。というお話でした。

*1:yieldは予約語ではなく変数名として利用可能だったりする、誰も使わないだろうけどw

*2:ちなみにRubyではFiberという名でコルーチンがサポートされている。