C# State Machine

はじめに

C#用の状態機械を考えてみます. 経験では, 階層化やマルチスレッド対応などの高度な機能が役に立ったことはないです. 単純で使いやすいものが欲しいです, 高度な機能は外側で実装すればいいと思います.

できたもの>BoringSM. 方針は次のようになります.

  • マルチスレッドに対応しない
  • 階層化などの高度な機能は提供しない
  • ランタイムにエラーを出さない. ただし, アサーションでできるだけ不正な操作を排除する.

使い方

enumで状態の名前を作成して, “状態名”+接尾語, “_Init”, “_Proc”, “_Term”, のメソッドを定義します. 自身の型とenumの型をジェネリクスに指定して状態機械クラスを作成し, コンストラクタに自身を渡します. 状態の定期更新はupdate, 任意の状態に遷移するときはsetメソッドで次の状態を指定します. 各メソッドはそれぞれ, 状態の初期化時, 状態の更新時, 状態の遷移時の時点で呼び出されます.

各メソッドは定義されていない場合は呼び出そうとしない仕様です. 例えば, 何もしない状態を作りたい場合は単にメソッドを定義しなければいいです. “_Init"内でsetメソッドを呼び出した場合は, “_Proc"を呼ばずに次の状態に即座に遷移します.

namespace sample
{
    public class Sample
    {
        private enum State
        {
            Init,
            State0,
            State1,
        };
        BoringSM.BoringSM<Sample, State> state_;

        public SampleSM()
        {
            state_ = new BoringSM<SampleSM, State>(this);
        }

        public void update()
        {
            state_.update();
        }

        public bool IsEnd
        {
            get { return (int)State.State1 == state_.get();}
        }

        private void Init_Init()
        {
            state_.set(State.State0); //Chaining inits is acceptable.
        }

        //private void Init_Proc(){} //Not totally necessary
        //private void Init_Term(){} //Not totally necessary

        //private void State0_Init(){} //Not totally necessary
        private void State0_Proc()
        {
            state_.set(State.State1);
        }
        private void State0_Term()
        {
        }
    };
}

実装

リフレクションで名前からメソッドを検索しているだけですが, 一点だけ工夫があります. Methodinfo.Invokeでメソッド呼び出しを行った場合, リフレクションで型チェックを行うため遅く, メモリアロケーションも発生します. System.Delegate.CreateDelegateでデリゲートに変換した方が速いです.