C#での簡単なステートマシンの例


258

更新:

再び例に感謝します。これらは非常に役に立ちました。以下のことから、私はそれらから何かを取り除くつもりはありません。

私がそれらを理解している限り、現在与えられている例と状態機械は、私たちが通常状態機械で理解しているものの半分だけではありませんか?
例は状態を変更しますが、それは変数の値を変更することによってのみ表され(そして異なる値-異なる状態での変更を許可することによって)示されるという意味で、通常、状態マシンはその動作も変更する必要があり、状態に応じて変数の異なる値の変更を許可するという意味ですが、異なる状態に対して異なるメソッドを実行できるという意味です。

それとも、ステートマシンとその一般的な使用について誤解がありますか?

宜しくお願いします


元の質問:

私は、c#のステートマシンとイテレーターブロック、およびステートマシンを作成するためのツールと、C#にはないものについてのこのディスカッションを見つけたので、多くの抽象的なものを見つけましたが、noobとして、これらすべてが少し混乱しています。

したがって、要旨を理解するためだけに、おそらく3,4の状態を持つ単純な状態機械を実現するC#ソースコードの例を誰かが提供できればすばらしいと思います。



一般的なステートマシン、または単にイテレータベースのマシンについて疑問に思っていますか?
Skurmedel、

2
例、のDAG daigramなどとネットコアステートレスlibにあり-価値見直しは:hanselman.com/blog/...
zmische

回答:


416

この簡単な状態図から始めましょう。

簡単な状態機械図

我々は持っています:

  • 4つの状態(非アクティブ、アクティブ、一時停止、終了)
  • 5種類の状態遷移(開始コマンド、終了コマンド、一時停止コマンド、再開コマンド、終了コマンド)。

これをC#に変換するには、現在の状態とコマンドでswitchステートメントを実行するか、遷移テーブルで遷移を検索するなど、いくつかの方法があります。この単純なステートマシンでは、遷移表を使用しDictionaryます。

using System;
using System.Collections.Generic;

namespace Juliet
{
    public enum ProcessState
    {
        Inactive,
        Active,
        Paused,
        Terminated
    }

    public enum Command
    {
        Begin,
        End,
        Pause,
        Resume,
        Exit
    }

    public class Process
    {
        class StateTransition
        {
            readonly ProcessState CurrentState;
            readonly Command Command;

            public StateTransition(ProcessState currentState, Command command)
            {
                CurrentState = currentState;
                Command = command;
            }

            public override int GetHashCode()
            {
                return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                StateTransition other = obj as StateTransition;
                return other != null && this.CurrentState == other.CurrentState && this.Command == other.Command;
            }
        }

        Dictionary<StateTransition, ProcessState> transitions;
        public ProcessState CurrentState { get; private set; }

        public Process()
        {
            CurrentState = ProcessState.Inactive;
            transitions = new Dictionary<StateTransition, ProcessState>
            {
                { new StateTransition(ProcessState.Inactive, Command.Exit), ProcessState.Terminated },
                { new StateTransition(ProcessState.Inactive, Command.Begin), ProcessState.Active },
                { new StateTransition(ProcessState.Active, Command.End), ProcessState.Inactive },
                { new StateTransition(ProcessState.Active, Command.Pause), ProcessState.Paused },
                { new StateTransition(ProcessState.Paused, Command.End), ProcessState.Inactive },
                { new StateTransition(ProcessState.Paused, Command.Resume), ProcessState.Active }
            };
        }

        public ProcessState GetNext(Command command)
        {
            StateTransition transition = new StateTransition(CurrentState, command);
            ProcessState nextState;
            if (!transitions.TryGetValue(transition, out nextState))
                throw new Exception("Invalid transition: " + CurrentState + " -> " + command);
            return nextState;
        }

        public ProcessState MoveNext(Command command)
        {
            CurrentState = GetNext(command);
            return CurrentState;
        }
    }


    public class Program
    {
        static void Main(string[] args)
        {
            Process p = new Process();
            Console.WriteLine("Current State = " + p.CurrentState);
            Console.WriteLine("Command.Begin: Current State = " + p.MoveNext(Command.Begin));
            Console.WriteLine("Command.Pause: Current State = " + p.MoveNext(Command.Pause));
            Console.WriteLine("Command.End: Current State = " + p.MoveNext(Command.End));
            Console.WriteLine("Command.Exit: Current State = " + p.MoveNext(Command.Exit));
            Console.ReadLine();
        }
    }
}

個人的な好みの問題として、私は私のステートマシンを設計したいとGetNext次の状態を返す関数決定論、およびMoveNextステートマシンを変異させる機能。


66
GetHashCode()素数を使用して正しく実装するための+1 。
ja72、2011年

13
GetHashCode()の目的を説明していただけませんか?
Siddharth

14
@Siddharth:StateTransitionクラスは辞書のキーとして使用され、キーの同等性が重要です。2つの異なるインスタンスがStateTransition同じであれば、それらが同じ遷移表す(例えばように考慮されるべきCurrentStateCommand同じです)。同等性を実装するにはEquals、同様にオーバーライドする必要がありますGetHashCode。特に、ディクショナリはハッシュコードを使用し、2つの等しいオブジェクトは同じハッシュコードを返す必要があります。等しくないオブジェクトが同じハッシュコードを共有していない場合も、良好なパフォーマンスが得GetHashCodeられます。
Martin Liversage

14
これで確かにステートマシン(および適切なC#の実装)が手に入りますが、動作の変更に関するOPの質問に対する答えがまだ欠けているように感じますか?結局のところ、状態を計算するだけですが、状態の変化に関連する動作、プログラムの実際の機能であり、通常はEntry / Exitイベントと呼ばれますが、まだ不足しています。
stijn

2
もし誰かがそれを必要とするなら、私はこのテートマシンを調整して、私のユニティゲームで使用しました。gitハブで利用可能です:github.com/MarcoMig/Finite-State-Machine-FSM
Max_Power89

73

既存のオープンソースの有限状態機械の1つを使用することもできます。たとえば、http ://code.google.com/p/bbvcommon/wiki/StateMachineにあるbbv.Common.StateMachine 。それは非常に直感的な流暢な構文と、出入りアクション、遷移アクション、ガード、階層的、パッシブ実装(呼び出し元のスレッドで実行)、アクティブ実装(fsmが実行される独自のスレッド)などの多くの機能を備えています。イベントがキューに追加されます)。

ジュリエットを例にとると、状態マシンの定義は非常に簡単になります。

var fsm = new PassiveStateMachine<ProcessState, Command>();
fsm.In(ProcessState.Inactive)
   .On(Command.Exit).Goto(ProcessState.Terminated).Execute(SomeTransitionAction)
   .On(Command.Begin).Goto(ProcessState.Active);
fsm.In(ProcessState.Active)
   .ExecuteOnEntry(SomeEntryAction)
   .ExecuteOnExit(SomeExitAction)
   .On(Command.End).Goto(ProcessState.Inactive)
   .On(Command.Pause).Goto(ProcessState.Paused);
fsm.In(ProcessState.Paused)
   .On(Command.End).Goto(ProcessState.Inactive).OnlyIf(SomeGuard)
   .On(Command.Resume).Goto(ProcessState.Active);
fsm.Initialize(ProcessState.Inactive);
fsm.Start();

fsm.Fire(Command.Begin);

更新:プロジェクトの場所はhttps://github.com/appccelerate/statemachineに移動しました


4
この優れたオープンソースステートマシンを参照していただき、ありがとうございます。どのようにして現在の状態を取得できますか?
ラマザンポラット

3
できませんし、すべきではありません。状態が不安定です。状態を要求すると、遷移の最中である可能性があります。すべてのアクションは、遷移、状態の開始、および状態の終了の中で行う必要があります。本当に状態が必要な場合は、ローカルフィールドを追加して、入力アクションで状態を割り当てることができます。
Remo Gloor

4
問題は、何が「必要」であり、本当にSM状態または他の種類の状態が必要かどうかです。たとえば、いくつかの表示テキストが必要な場合、たとえば、送信の準備に複数のサブ状態がある場合、いくつかの表示テキストが同じ表示テキストを持つ可能性があります。この場合は、意図したとおりに行う必要があります。一部の表示テキストを正しい場所で更新します。たとえば、ExecuteOnEntry内です。これ以上の情報が必要な場合は、新しい質問をして、問題がここに出てきているので、問題を正確に述べてください。
Remo Gloor

わかりました。新しい質問をして、返信を待っています。あなたが最良の答えを持っているので質問者が受け入れなかったので、私は他の誰かがこの問題を解決するとは思わないからです。こちらに質問URLを掲載します。ありがとう。
ラマザン

4
流暢で宣言的なAPIの+1。それは素晴らしいです。ところで、グーグルコードは古くなっているようです。彼らの最新のプロジェクトサイトは、ここ
KFL 2014

52

これは、非常に単純化された電子デバイス(TVなど)をモデル化した、非常に古典的な有限状態機械の例です

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace fsm
{
class Program
{
    static void Main(string[] args)
    {
        var fsm = new FiniteStateMachine();
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.PlugIn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOff);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.RemovePower);
        Console.WriteLine(fsm.State);
        Console.ReadKey();
    }

    class FiniteStateMachine
    {
        public enum States { Start, Standby, On };
        public States State { get; set; }

        public enum Events { PlugIn, TurnOn, TurnOff, RemovePower };

        private Action[,] fsm;

        public FiniteStateMachine()
        {
            this.fsm = new Action[3, 4] { 
                //PlugIn,       TurnOn,                 TurnOff,            RemovePower
                {this.PowerOn,  null,                   null,               null},              //start
                {null,          this.StandbyWhenOff,    null,               this.PowerOff},     //standby
                {null,          null,                   this.StandbyWhenOn, this.PowerOff} };   //on
        }
        public void ProcessEvent(Events theEvent)
        {
            this.fsm[(int)this.State, (int)theEvent].Invoke();
        }

        private void PowerOn() { this.State = States.Standby; }
        private void PowerOff() { this.State = States.Start; }
        private void StandbyWhenOn() { this.State = States.Standby; }
        private void StandbyWhenOff() { this.State = States.On; }
    }
}
}

6
ステートマシンを初めて使う人にとって、これは最初に足を濡らす優れた最初の例です。
PositiveGuy

2
私はステートマシンに不慣れで、真剣に、これが私に光をもたらしました-ありがとうございます!
MC5

1
私はこの実装が好きでした。これにつまずくかもしれない人のために、わずかな「改善」。FSMクラスではprivate void DoNothing() {return;}、nullのすべてのインスタンスを追加してに置き換えましたthis.DoNothing。現在の状態に戻すという楽しい副作用があります。
Sethmo011 2015年

1
これらの名前のいくつかの背後に理由があるのだろうかと思います。これを見ると、最初の直感はtoの要素の名前をに変更するStatesことUnpowered, Standby, Onです。私の考えは、誰かが私のテレビの状態を尋ねた場合、私は「開始」ではなく「オフ」と言うでしょう。私も変更StandbyWhenOnStandbyWhenOffするTurnOnTurnOff。これにより、コードがより直感的に読み取れるようになりますが、私の用語を適切でなくする慣習やその他の要因があるのでしょうか。
Jason Hamje

妥当なようですが、私は実際には州の命名規則に従っていませんでした。名前は、モデル化するものすべてに意味があります。
PeteStensønes、

20

ここでは恥知らずな自己宣伝がいくつかありますが、少し前にYieldMachineと呼ばれるライブラリを作成しました。これにより、複雑さが限定された状態マシンを非常にクリーンでシンプルな方法で記述できます。たとえば、ランプについて考えてみましょう。

ランプの状態機械

この状態マシンには2つのトリガーと3つの状態があることに注意してください。YieldMachineコードでは、すべての状態関連の動作に対して単一のメソッドを記述します。この方法ではgoto、各状態に使用するという恐ろしい残虐行為を実行します。トリガーは、Actionと呼ばれる属性で装飾されたタイプのプロパティまたはフィールドになりますTrigger。以下の最初の状態とその遷移のコードにコメントしました。次の状態は同じパターンに従います。

public class Lamp : StateMachine
{
    // Triggers (or events, or actions, whatever) that our
    // state machine understands.
    [Trigger]
    public readonly Action PressSwitch;

    [Trigger]
    public readonly Action GotError;

    // Actual state machine logic
    protected override IEnumerable WalkStates()
    {
    off:                                       
        Console.WriteLine("off.");
        yield return null;

        if (Trigger == PressSwitch) goto on;
        InvalidTrigger();

    on:
        Console.WriteLine("*shiiine!*");
        yield return null;

        if (Trigger == GotError) goto error;
        if (Trigger == PressSwitch) goto off;
        InvalidTrigger();

    error:
        Console.WriteLine("-err-");
        yield return null;

        if (Trigger == PressSwitch) goto off;
        InvalidTrigger();
    }
}

短くていいですね

このステートマシンは、トリガーを送信するだけで制御されます。

var sm = new Lamp();
sm.PressSwitch(); //go on
sm.PressSwitch(); //go off

sm.PressSwitch(); //go on
sm.GotError();    //get error
sm.PressSwitch(); //go off

明確にするために、これを使用する方法を理解するのに役立つように、最初の状態にコメントをいくつか追加しました。

    protected override IEnumerable WalkStates()
    {
    off:                                       // Each goto label is a state

        Console.WriteLine("off.");             // State entry actions

        yield return null;                     // This means "Wait until a 
                                               // trigger is called"

                                               // Ah, we got triggered! 
                                               //   perform state exit actions 
                                               //   (none, in this case)

        if (Trigger == PressSwitch) goto on;   // Transitions go here: 
                                               // depending on the trigger 
                                               // that was called, go to
                                               // the right state

        InvalidTrigger();                      // Throw exception on 
                                               // invalid trigger

        ...

これは、C#コンパイラが実際にを使用する各メソッドの内部でステートマシンを作成したために機能しますyield return。このコンストラクトは通常、データのシーケンスを遅延して作成するために使用されますが、この場合、実際には返されたシーケンス(とにかくすべてnullです)ではなく、内部で作成される状態の動作に関心があります。

StateMachine基本クラスは、それぞれに割り当てるコードと構造上のいくつかの反射を行う[Trigger]設定動作、Trigger部材を前方ステートマシンを移動させます。

しかし、それを使用するために内部を理解する必要はありません。


2
「goto」はメソッド間をジャンプする場合にのみ、ひどいです。幸い、C#では許可されていません。
ブラノン2013年

いい視点ね!実際、静的に型付けされた言語gotoでメソッド間を許可できるとしたら、私はとても感心します。
skrebbel 2013年

3
@Brannon:gotoメソッド間をジャンプできる言語はどれですか?それがどのように機能するかはわかりません。いいえ、gotoそれは手続き型プログラミングになり(これだけで単体テストのような素晴らしいことを複雑にします)、コードの繰り返しを促進し(InvalidTriggerすべての状態に挿入する必要があることに気づきましたか?)、最後にプログラムフローの追跡が難しくなるため、問題があります。これをこのスレッドの(ほとんどの)他のソリューションと比較すると、これがFSM全体が単一のメソッドで発生する唯一のソリューションであることがわかります。それは通常、懸念を提起するのに十分です。
Groo、2014年

1
@ Groo、GW-BASICなど。メソッドも関数もないことを助けます。その上、この例で「プログラムフローの追跡が難しい」と思う理由を理解するのは非常に困難です。これは状態マシンであり、別の状態から状態に「移動」することが、あなたが行う唯一のことです。これはgotoかなりよくマップされます。
skrebbel 2014年

3
GW-BASICはgoto関数間をジャンプできますが、関数をサポートしていませんか?:)そうです、「追跡するのが難しい」という発言はより一般的なgoto問題であり、実際、この場合はそれほど問題ではありません。
Groo、2014年

13

コードブロックをオーケストレーション形式で実行できるようにするイテレーターブロックをコーディングできます。コードブロックがどのように分割されるかは、実際には何に対応する必要もありません。それは、コーディングする方法にすぎません。例えば:

IEnumerable<int> CountToTen()
{
    System.Console.WriteLine("1");
    yield return 0;
    System.Console.WriteLine("2");
    System.Console.WriteLine("3");
    System.Console.WriteLine("4");
    yield return 0;
    System.Console.WriteLine("5");
    System.Console.WriteLine("6");
    System.Console.WriteLine("7");
    yield return 0;
    System.Console.WriteLine("8");
    yield return 0;
    System.Console.WriteLine("9");
    System.Console.WriteLine("10");
}

この場合、CountToTenを呼び出しても、実際には何も実行されません。得られるのは、事実上、ステートマシンの新しいインスタンスを作成できるステートマシンジェネレーターです。これを行うには、GetEnumerator()を呼び出します。結果のIEnumeratorは事実上、MoveNext(...)を呼び出すことによって駆動できる状態マシンです。

したがって、この例では、MoveNext(...)を初めて呼び出すときに「1」がコンソールに書き込まれ、次にMoveNext(...)を呼び出すときに2、3、4、および次に、5、6、7、8、9、10と表示されます。ご覧のとおり、これは、発生する状況を調整するための便利なメカニズムです。



8

これは別の見方をしたステートマシンであるため、ここで別の回答を投稿します。非常に視覚的です。

私の元の答えは、古典的な命令コードです。ステートマシンの視覚化を簡単にする配列のため、コードが進むにつれてかなり視覚的になると思います。欠点は、これをすべて記述する必要があることです。 Remosの答えは、ボイラープレートコードを記述する労力を軽減しますが、視覚的ではありません。3番目の選択肢があります。本当にステートマシンを描いています。

.NETを使用していて、ランタイムのバージョン4をターゲットにできる場合は、ワークフローのステートマシンアクティビティを使用するオプションがあります。これらは本質的に、ステートマシン(ジュリエットの図のように)を描画し、WFランタイムに実行させることができます。

詳細については、MSDNの記事「Windows Workflow Foundationを使用したステートマシンの構築」、および最新バージョンについては、このCodePlexサイトを参照してください。

これは、.NETを対象とする場合に常に好むオプションです。なぜなら、プログラマー以外の人に見やすく、変更し、説明しやすいからです。彼らが言うように写真は千の言葉の価値があります!


ステートマシンは、ワークフローの基盤全体の中で最も優れた部分の1つだと思います。
fabsenet 2015

7

ステートマシンは抽象概念であり、それを作成するのに特定のツールは必要ありませんが、ツールは役立つ場合があることを覚えておくと役立ちます。

たとえば、次の機能を持つステートマシンを実現できます。

void Hunt(IList<Gull> gulls)
{
    if (gulls.Empty())
       return;

    var target = gulls.First();
    TargetAcquired(target, gulls);
}

void TargetAcquired(Gull target, IList<Gull> gulls)
{
    var balloon = new WaterBalloon(weightKg: 20);

    this.Cannon.Fire(balloon);

    if (balloon.Hit)
    {
       TargetHit(target, gulls);
    }
    else
       TargetMissed(target, gulls);
}

void TargetHit(Gull target, IList<Gull> gulls)
{
    Console.WriteLine("Suck on it {0}!", target.Name);
    Hunt(gulls);
}

void TargetMissed(Gull target, IList<Gull> gulls)
{
    Console.WriteLine("I'll get ya!");
    TargetAcquired(target, gulls);
}

この機械はカモメを探し、水風船で打つことを試みました。それが見当たらない場合は、当たるまで1つを発射しようとします(現実的な期待で実行できます;))、それ以外の場合はコンソールで膨れ上がります。嫌がらせをするカモメがなくなるまで狩りを続けます。

各関数は各状態に対応しています。開始および終了(または受け入れ)状態は表示されません。おそらく、関数によってモデル化された状態よりも多くの状態があります。たとえば、バルーンを発射した後、マシンは実際には以前とは別の状態にありますが、この区別を行うのは実用的ではないと判断しました。

一般的な方法は、クラスを使用して状態を表し、それらをさまざまな方法で接続することです。


7

この素晴らしいチュートリアルをオンラインで見つけて、有限状態マシンに頭を悩ませました。

http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867

このチュートリアルは言語に依存しないため、C#のニーズに簡単に適合させることができます。

また、使用例(食べ物を探すアリ)もわかりやすいです。


チュートリアルから:

ここに画像の説明を入力してください

public class FSM {
    private var activeState :Function; // points to the currently active state function

    public function FSM() {
    }

    public function setState(state :Function) :void {
        activeState = state;
    }

    public function update() :void {
        if (activeState != null) {
            activeState();
        }
    }
}


public class Ant
{
    public var position   :Vector3D;
    public var velocity   :Vector3D;
    public var brain      :FSM;

    public function Ant(posX :Number, posY :Number) {
        position    = new Vector3D(posX, posY);
        velocity    = new Vector3D( -1, -1);
        brain       = new FSM();

        // Tell the brain to start looking for the leaf.
        brain.setState(findLeaf);
    }

    /**
    * The "findLeaf" state.
    * It makes the ant move towards the leaf.
    */
    public function findLeaf() :void {
        // Move the ant towards the leaf.
        velocity = new Vector3D(Game.instance.leaf.x - position.x, Game.instance.leaf.y - position.y);

        if (distance(Game.instance.leaf, this) <= 10) {
            // The ant is extremelly close to the leaf, it's time
            // to go home.
            brain.setState(goHome);
        }

        if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) {
            // Mouse cursor is threatening us. Let's run away!
            // It will make the brain start calling runAway() from
            // now on.
            brain.setState(runAway);
        }
    }

    /**
    * The "goHome" state.
    * It makes the ant move towards its home.
    */
    public function goHome() :void {
        // Move the ant towards home
        velocity = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y);

        if (distance(Game.instance.home, this) <= 10) {
            // The ant is home, let's find the leaf again.
            brain.setState(findLeaf);
        }
    }

    /**
    * The "runAway" state.
    * It makes the ant run away from the mouse cursor.
    */
    public function runAway() :void {
        // Move the ant away from the mouse cursor
        velocity = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y);

        // Is the mouse cursor still close?
        if (distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) {
            // No, the mouse cursor has gone away. Let's go back looking for the leaf.
            brain.setState(findLeaf);
        }
    }

    public function update():void {
        // Update the FSM controlling the "brain". It will invoke the currently
        // active state function: findLeaf(), goHome() or runAway().
        brain.update();

        // Apply the velocity vector to the position, making the ant move.
        moveBasedOnVelocity();
    }

    (...)
}

1
このリンクで質問に答えることができますが、回答の重要な部分をここに含め、参照用のリンクを提供することをお勧めします。リンクされたページが変更されると、リンクのみの回答が無効になる可能性があります。- レビューから
drneel

@drneelチュートリアルからビットをコピーして貼り付けることができます...しかし、それは著者から信用を奪うものではありませんか?
ジェットブルー

1
@JetBlue:回答のリンクを参照として残し、他の人の著作権を侵害しないように、回答の投稿に自分の言葉に関連するビットを含めます。私はそれが厳しいように見えるのを知っています、しかし多くの答えはこのルールのためにはるかに良くなりました。
Flimm

6

今日は、ステートデザインパターンについて詳しく説明します。私はなかったからピクチャに記載されているように、C#でスレッドに(+/-)等しくThreadState、試験されたC#でスレッドを

ここに画像の説明を入力してください

新しい状態を簡単に追加でき、状態の実装にカプセル化されているため、ある状態から別の状態への移動を構成するのは非常に簡単です。

実装と使用:状態設計パターンによる.NET ThreadStateの実装


1
リンクが死んでいる。他にありますか?
2017

5

C#でFSMを実装することはまだ試みていませんが、これらはすべて、CまたはASMなどの低レベル言語でのFSMの扱い方が非常に複雑(または外観)です。

私がいつも知っている方法は「反復ループ」のようなものと呼ばれていると思います。その中には、本質的にイベント(割り込み)に基づいて定期的に終了し、再びメインループに戻る「while」ループがあります。

割り込みハンドラー内で、CurrentStateを渡してNextStateを返し、メインループのCurrentState変数を上書きします。これは、プログラムが終了する(またはマイクロコントローラーがリセットされる)まで無限に続きます。

私が他の答えを見ているのは、FSMが私の考えでは実装を意図している方法と比較して、すべて非常に複雑に見えます。その美しさはその単純さにあり、FSMは非常に複雑で、多くの多くの状態と遷移があり得ますが、複雑なプロセスを簡単に分解して消化することができます。

私の回答には別の質問を含めるべきではないことに気づきましたが、私は尋ねざるを得ません:なぜこれらの他の提案されたソリューションはそれほど複雑に見えるのですか?
彼らは巨大なそりハンマーで小さな釘を打つようなもののようです。


1
完全に同意します。switchステートメントを使用した単純なwhileループは、可能な限り単純です。
2017

2
多くの状態と条件を備えた非常に複雑な状態機械がない限り、複数の入れ子になったスイッチができます。また、ループの実装によっては、ビジー待機でペナルティが発生する場合があります。
Sune Rievers 2017

3

なんと一味のStatePattern。それはあなたのニーズに合っていますか?

そのコンテキストは関連していると思いますが、確かに試してみる価値はあります。

http://en.wikipedia.org/wiki/State_pattern

これにより、州は「オブジェクト」クラスではなく、どこへ行くかを決定できます。

ブルーノ


1
状態パターンは、その状態/モードに基づいて異なる動作をするクラスを扱います。状態間の遷移は扱いません。
Eli Algranti 2013

3

私の意見では、ステートマシンは状態を変更することだけでなく、特定の状態内のトリガー/イベントを処理することも(非常に重要)意味します。ステートマシンのデザインパターンをよりよく理解したい場合は、320ページのHead First Design Patterns」を参照してください

変数内の状態だけでなく、さまざまな状態内のトリガーの処理についてもです。わかりやすい説明が含まれている素晴らしい章(そして、これについて言及しても料金はかかりません:-)。


3

私はこれを貢献しました:

https://code.google.com/p/ysharp/source/browse/#svn%2Ftrunk%2FStateMachinesPoC

以下は、IObserver(of signal)として状態を使用して、コマンドの直接送信と間接送信をデモする例の1つです。したがって、信号ソースIObservable(of signal)に応答します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    using Machines;

    public static class WatchingTvSampleAdvanced
    {
        // Enum type for the transition triggers (instead of System.String) :
        public enum TvOperation { Plug, SwitchOn, SwitchOff, Unplug, Dispose }

        // The state machine class type is also used as the type for its possible states constants :
        public class Television : NamedState<Television, TvOperation, DateTime>
        {
            // Declare all the possible states constants :
            public static readonly Television Unplugged = new Television("(Unplugged TV)");
            public static readonly Television Off = new Television("(TV Off)");
            public static readonly Television On = new Television("(TV On)");
            public static readonly Television Disposed = new Television("(Disposed TV)");

            // For convenience, enter the default start state when the parameterless constructor executes :
            public Television() : this(Television.Unplugged) { }

            // To create a state machine instance, with a given start state :
            private Television(Television value) : this(null, value) { }

            // To create a possible state constant :
            private Television(string moniker) : this(moniker, null) { }

            private Television(string moniker, Television value)
            {
                if (moniker == null)
                {
                    // Build the state graph programmatically
                    // (instead of declaratively via custom attributes) :
                    Handler<Television, TvOperation, DateTime> stateChangeHandler = StateChange;
                    Build
                    (
                        new[]
                        {
                            new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = stateChangeHandler },
                            new { From = Television.Unplugged, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.SwitchOn, Goto = Television.On, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.SwitchOff, Goto = Television.Off, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler }
                        },
                        false
                    );
                }
                else
                    // Name the state constant :
                    Moniker = moniker;
                Start(value ?? this);
            }

            // Because the states' value domain is a reference type, disallow the null value for any start state value : 
            protected override void OnStart(Television value)
            {
                if (value == null)
                    throw new ArgumentNullException("value", "cannot be null");
            }

            // When reaching a final state, unsubscribe from all the signal source(s), if any :
            protected override void OnComplete(bool stateComplete)
            {
                // Holds during all transitions into a final state
                // (i.e., stateComplete implies IsFinal) :
                System.Diagnostics.Debug.Assert(!stateComplete || IsFinal);

                if (stateComplete)
                    UnsubscribeFromAll();
            }

            // Executed before and after every state transition :
            private void StateChange(IState<Television> state, ExecutionStep step, Television value, TvOperation info, DateTime args)
            {
                // Holds during all possible transitions defined in the state graph
                // (i.e., (step equals ExecutionStep.LeaveState) implies (not state.IsFinal))
                System.Diagnostics.Debug.Assert((step != ExecutionStep.LeaveState) || !state.IsFinal);

                // Holds in instance (i.e., non-static) transition handlers like this one :
                System.Diagnostics.Debug.Assert(this == state);

                switch (step)
                {
                    case ExecutionStep.LeaveState:
                        var timeStamp = ((args != default(DateTime)) ? String.Format("\t\t(@ {0})", args) : String.Empty);
                        Console.WriteLine();
                        // 'value' is the state value that we are transitioning TO :
                        Console.WriteLine("\tLeave :\t{0} -- {1} -> {2}{3}", this, info, value, timeStamp);
                        break;
                    case ExecutionStep.EnterState:
                        // 'value' is the state value that we have transitioned FROM :
                        Console.WriteLine("\tEnter :\t{0} -- {1} -> {2}", value, info, this);
                        break;
                    default:
                        break;
                }
            }

            public override string ToString() { return (IsConstant ? Moniker : Value.ToString()); }
        }

        public static void Run()
        {
            Console.Clear();

            // Create a signal source instance (here, a.k.a. "remote control") that implements
            // IObservable<TvOperation> and IObservable<KeyValuePair<TvOperation, DateTime>> :
            var remote = new SignalSource<TvOperation, DateTime>();

            // Create a television state machine instance (automatically set in a default start state),
            // and make it subscribe to a compatible signal source, such as the remote control, precisely :
            var tv = new Television().Using(remote);
            bool done;

            // Always holds, assuming the call to Using(...) didn't throw an exception (in case of subscription failure) :
            System.Diagnostics.Debug.Assert(tv != null, "There's a bug somewhere: this message should never be displayed!");

            // As commonly done, we can trigger a transition directly on the state machine :
            tv.MoveNext(TvOperation.Plug, DateTime.Now);

            // Alternatively, we can also trigger transitions by emitting from the signal source / remote control
            // that the state machine subscribed to / is an observer of :
            remote.Emit(TvOperation.SwitchOn, DateTime.Now);
            remote.Emit(TvOperation.SwitchOff);
            remote.Emit(TvOperation.SwitchOn);
            remote.Emit(TvOperation.SwitchOff, DateTime.Now);

            done =
                (
                    tv.
                        MoveNext(TvOperation.Unplug).
                        MoveNext(TvOperation.Dispose) // MoveNext(...) returns null iff tv.IsFinal == true
                    == null
                );

            remote.Emit(TvOperation.Unplug); // Ignored by the state machine thanks to the OnComplete(...) override above

            Console.WriteLine();
            Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done);

            Console.WriteLine();
            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }
    }
}

注:この例はかなり人工的なものであり、主に多数の直交機能をデモすることを目的としています。このようなCRTP(http://en.wikipedia.org/wiki/Curiously_recurring_template_patternを参照)を使用して、完全なクラスによって状態値ドメイン自体を実装する必要はほとんどありません。

これは、同じステートマシンで、同じテストケースを使用した、より単純で、おそらくより一般的な実装のユースケース(単純な列挙型を状態値ドメインとして使用)の場合です。

https://code.google.com/p/ysharp/source/browse/trunk/StateMachinesPoC/WatchingTVSample.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    using Machines;

    public static class WatchingTvSample
    {
        public enum Status { Unplugged, Off, On, Disposed }

        public class DeviceTransitionAttribute : TransitionAttribute
        {
            public Status From { get; set; }
            public string When { get; set; }
            public Status Goto { get; set; }
            public object With { get; set; }
        }

        // State<Status> is a shortcut for / derived from State<Status, string>,
        // which in turn is a shortcut for / derived from State<Status, string, object> :
        public class Device : State<Status>
        {
            // Executed before and after every state transition :
            protected override void OnChange(ExecutionStep step, Status value, string info, object args)
            {
                if (step == ExecutionStep.EnterState)
                {
                    // 'value' is the state value that we have transitioned FROM :
                    Console.WriteLine("\t{0} -- {1} -> {2}", value, info, this);
                }
            }

            public override string ToString() { return Value.ToString(); }
        }

        // Since 'Device' has no state graph of its own, define one for derived 'Television' :
        [DeviceTransition(From = Status.Unplugged, When = "Plug", Goto = Status.Off)]
        [DeviceTransition(From = Status.Unplugged, When = "Dispose", Goto = Status.Disposed)]
        [DeviceTransition(From = Status.Off, When = "Switch On", Goto = Status.On)]
        [DeviceTransition(From = Status.Off, When = "Unplug", Goto = Status.Unplugged)]
        [DeviceTransition(From = Status.Off, When = "Dispose", Goto = Status.Disposed)]
        [DeviceTransition(From = Status.On, When = "Switch Off", Goto = Status.Off)]
        [DeviceTransition(From = Status.On, When = "Unplug", Goto = Status.Unplugged)]
        [DeviceTransition(From = Status.On, When = "Dispose", Goto = Status.Disposed)]
        public class Television : Device { }

        public static void Run()
        {
            Console.Clear();

            // Create a television state machine instance, and return it, set in some start state :
            var tv = new Television().Start(Status.Unplugged);
            bool done;

            // Holds iff the chosen start state isn't a final state :
            System.Diagnostics.Debug.Assert(tv != null, "The chosen start state is a final state!");

            // Trigger some state transitions with no arguments
            // ('args' is ignored by this state machine's OnChange(...), anyway) :
            done =
                (
                    tv.
                        MoveNext("Plug").
                        MoveNext("Switch On").
                        MoveNext("Switch Off").
                        MoveNext("Switch On").
                        MoveNext("Switch Off").
                        MoveNext("Unplug").
                        MoveNext("Dispose") // MoveNext(...) returns null iff tv.IsFinal == true
                    == null
                );

            Console.WriteLine();
            Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done);

            Console.WriteLine();
            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }
    }
}

'HTH


各状態インスタンスに状態グラフの独自のコピーがあるのは少し奇妙ではありませんか?
Groo、2014年

@Groo:いいえ、ありません。プライベートコンストラクターを使用してモニカーのnull文字列(つまり、保護された 'Build'メソッドを呼び出す)を使用して構築されたTelevisionのインスタンスのみが、状態マシンとして状態グラフを持ちます。その他の名前付きのテレビのインスタンス(その従来のアドホックな目的ではモニカ nullではない)は単なる(固定点)状態(いわば)であり、状態定数(状態グラフ)として機能します。実際のステートマシンは頂点として参照されます)。'HTH、
YSharp

わかったよ。とにかく、私見では、これらの遷移を実際に処理するコードを含めた方がいいでしょう。このように、それはあなたのライブラリに(IMHO)それほど明白ではないインターフェースを使用する例としてのみ機能します。たとえば、どのようにStateChange解決されますか?リフレクションを通して?それは本当に必要ですか?
Groo 2014年

1
@Groo:良い発言。最初の例ではハンドラーを正確に反映する必要はありません。プログラムで正確に行われ、静的にバインド/型チェックできるためです(カスタム属性を介した場合とは異なります)。この作品もそう期待通り: private Television(string moniker, Television value) { Handler<Television, TvOperation, DateTime> myHandler = StateChange; // (code omitted) new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = myHandler } }
YSharp

1
がんばってくれてありがとう!
Groo

3

Julietのコードからこの汎用ステートマシンを作成しました。それは私にとって素晴らしい働きをしています。

これらは利点です:

  • 2つの列挙型TStateとを使用してTCommand、コードで新しい状態マシンを作成できます。
  • メソッドのTransitionResult<TState>出力結果をより詳細に制御するための構造体を追加しました[Try]GetNext()
  • 入れ子になったクラスをさらすStateTransition だけを通してAddTransition(TState, TCommand, TState)それを仕事に簡単にそれを作ります

コード:

public class StateMachine<TState, TCommand>
    where TState : struct, IConvertible, IComparable
    where TCommand : struct, IConvertible, IComparable
{
    protected class StateTransition<TS, TC>
        where TS : struct, IConvertible, IComparable
        where TC : struct, IConvertible, IComparable
    {
        readonly TS CurrentState;
        readonly TC Command;

        public StateTransition(TS currentState, TC command)
        {
            if (!typeof(TS).IsEnum || !typeof(TC).IsEnum)
            {
                throw new ArgumentException("TS,TC must be an enumerated type");
            }

            CurrentState = currentState;
            Command = command;
        }

        public override int GetHashCode()
        {
            return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            StateTransition<TS, TC> other = obj as StateTransition<TS, TC>;
            return other != null
                && this.CurrentState.CompareTo(other.CurrentState) == 0
                && this.Command.CompareTo(other.Command) == 0;
        }
    }

    private Dictionary<StateTransition<TState, TCommand>, TState> transitions;
    public TState CurrentState { get; private set; }

    protected StateMachine(TState initialState)
    {
        if (!typeof(TState).IsEnum || !typeof(TCommand).IsEnum)
        {
            throw new ArgumentException("TState,TCommand must be an enumerated type");
        }

        CurrentState = initialState;
        transitions = new Dictionary<StateTransition<TState, TCommand>, TState>();
    }

    /// <summary>
    /// Defines a new transition inside this state machine
    /// </summary>
    /// <param name="start">source state</param>
    /// <param name="command">transition condition</param>
    /// <param name="end">destination state</param>
    protected void AddTransition(TState start, TCommand command, TState end)
    {
        transitions.Add(new StateTransition<TState, TCommand>(start, command), end);
    }

    public TransitionResult<TState> TryGetNext(TCommand command)
    {
        StateTransition<TState, TCommand> transition = new StateTransition<TState, TCommand>(CurrentState, command);
        TState nextState;
        if (transitions.TryGetValue(transition, out nextState))
            return new TransitionResult<TState>(nextState, true);
        else
            return new TransitionResult<TState>(CurrentState, false);
    }

    public TransitionResult<TState> MoveNext(TCommand command)
    {
        var result = TryGetNext(command);
        if(result.IsValid)
        {
            //changes state
            CurrentState = result.NewState;
        }
        return result;
    }
}

これは、TryGetNextメソッドの戻り型です。

public struct TransitionResult<TState>
{
    public TransitionResult(TState newState, bool isValid)
    {
        NewState = newState;
        IsValid = isValid;
    }
    public TState NewState;
    public bool IsValid;
}

使い方:

これはOnlineDiscountStateMachine、ジェネリッククラスからを作成する方法です。

OnlineDiscountState状態の列挙型OnlineDiscountCommandとコマンドの列挙型を定義します。

OnlineDiscountStateMachineこれら2つの列挙型を使用して、ジェネリッククラスから派生したクラスを定義します。

からコンストラクタを派生させbase(OnlineDiscountState.InitialState)て、初期状態を次のように設定します。OnlineDiscountState.InitialState

AddTransition必要なだけ何度でも使用

public class OnlineDiscountStateMachine : StateMachine<OnlineDiscountState, OnlineDiscountCommand>
{
    public OnlineDiscountStateMachine() : base(OnlineDiscountState.Disconnected)
    {
        AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Connected);
        AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Error_AuthenticationError);
        AddTransition(OnlineDiscountState.Connected, OnlineDiscountCommand.Submit, OnlineDiscountState.WaitingForResponse);
        AddTransition(OnlineDiscountState.WaitingForResponse, OnlineDiscountCommand.DataReceived, OnlineDiscountState.Disconnected);
    }
}

派生したステートマシンを使用する

    odsm = new OnlineDiscountStateMachine();
    public void Connect()
    {
        var result = odsm.TryGetNext(OnlineDiscountCommand.Connect);

        //is result valid?
        if (!result.IsValid)
            //if this happens you need to add transitions to the state machine
            //in this case result.NewState is the same as before
            Console.WriteLine("cannot navigate from this state using OnlineDiscountCommand.Connect");

        //the transition was successfull
        //show messages for new states
        else if(result.NewState == OnlineDiscountState.Error_AuthenticationError)
            Console.WriteLine("invalid user/pass");
        else if(result.NewState == OnlineDiscountState.Connected)
            Console.WriteLine("Connected");
        else
            Console.WriteLine("not implemented transition result for " + result.NewState);
    }

1

Julietによって提案されたステートマシンには誤りがあると思います。たとえば、GetHashCodeメソッドは、2つの異なる遷移に対して同じハッシュコードを返すことができます。次に例を示します。

状態=アクティブ(1)、コマンド=一時停止(2)=> HashCode = 17 + 31 + 62 = 110

状態=一時停止(2)、コマンド=終了(1)=> HashCode = 17 + 62 + 31 = 110

このエラーを回避するには、メソッドは次のようにする必要があります。

public override int GetHashCode()
   {
            return 17 + 23 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
   }

アレックス


1
ハッシュコードは、可能な組み合わせに対して一意の数値を返す必要はありません。ターゲット範囲全体にわたって適切な分布を持つ明確な値のみを返します(この場合、範囲はすべての可能なint値です)。そのため、HashCodeは常にとともに実装されEqualsます。ハッシュコードが同じである場合、オブジェクトはEqualsメソッドを使用して正確な等価性がチェックされます。
ドミトリーアヴトノモフ2018

0

FiniteStateMachineは、C#リンクで記述されたシンプルなステートマシンです。

利点tuは私のライブラリFiniteStateMachineを使用します。

  1. 「コンテキスト」クラスを定義して、外界に単一のインターフェースを提示します。
  2. State抽象基本クラスを定義します。
  3. 状態マシンのさまざまな「状態」を、State基本クラスの派生クラスとして表します。
  4. 適切なState派生クラスで状態固有の動作を定義します。
  5. 「コンテキスト」クラスの現在の「状態」へのポインタを維持します。
  6. ステートマシンの状態を変更するには、現在の「状態」ポインタを変更します。

DLLのダウンロードダウンロード

LINQPadの例:

void Main()
{
            var machine = new SFM.Machine(new StatePaused());
            var output = machine.Command("Input_Start", Command.Start);
            Console.WriteLine(Command.Start.ToString() + "->  State: " + machine.Current);
            Console.WriteLine(output);

            output = machine.Command("Input_Pause", Command.Pause);
            Console.WriteLine(Command.Pause.ToString() + "->  State: " + machine.Current);
            Console.WriteLine(output);
            Console.WriteLine("-------------------------------------------------");
}
    public enum Command
    {
        Start,
        Pause,
    }

    public class StateActive : SFM.State
    {

        public override void Handle(SFM.IContext context)

        {
            //Gestione parametri
            var input = (String)context.Input;
            context.Output = input;

            //Gestione Navigazione
            if ((Command)context.Command == Command.Pause) context.Next = new StatePaused();
            if ((Command)context.Command == Command.Start) context.Next = this;

        }
    }


public class StatePaused : SFM.State
{

     public override void Handle(SFM.IContext context)

     {

         //Gestione parametri
         var input = (String)context.Input;
         context.Output = input;

         //Gestione Navigazione
         if ((Command)context.Command == Command.Start) context.Next = new  StateActive();
         if ((Command)context.Command == Command.Pause) context.Next = this;


     }

 }

1
GNU GPLライセンスがあります。
Der_Meister 2017年

0

state.csをお勧めします。私はstate.js(JavaScriptバージョン)を個人的に使用しており、非常に満足しています。そのC#バージョンも同様に機能します。

状態をインスタンス化します。

        // create the state machine
        var player = new StateMachine<State>( "player" );

        // create some states
        var initial = player.CreatePseudoState( "initial", PseudoStateKind.Initial );
        var operational = player.CreateCompositeState( "operational" );
        ...

いくつかの遷移をインスタンス化します。

        var t0 = player.CreateTransition( initial, operational );
        player.CreateTransition( history, stopped );
        player.CreateTransition<String>( stopped, running, ( state, command ) => command.Equals( "play" ) );
        player.CreateTransition<String>( active, stopped, ( state, command ) => command.Equals( "stop" ) );

状態と遷移に対するアクションを定義します。

    t0.Effect += DisengageHead;
    t0.Effect += StopMotor;

そして、それは(かなり)それです。詳細については、ウェブサイトをご覧ください。



0

このリポジトリの他の代替https://github.com/lingkodsoft/StateBliss は流暢な構文を使用し、トリガーをサポートしています。

    public class BasicTests
    {
        [Fact]
        public void Tests()
        {
            // Arrange
            StateMachineManager.Register(new [] { typeof(BasicTests).Assembly }); //Register at bootstrap of your application, i.e. Startup
            var currentState = AuthenticationState.Unauthenticated;
            var nextState = AuthenticationState.Authenticated;
            var data = new Dictionary<string, object>();

            // Act
            var changeInfo = StateMachineManager.Trigger(currentState, nextState, data);

            // Assert
            Assert.True(changeInfo.StateChangedSucceeded);
            Assert.Equal("ChangingHandler1", changeInfo.Data["key1"]);
            Assert.Equal("ChangingHandler2", changeInfo.Data["key2"]);
        }

        //this class gets regitered automatically by calling StateMachineManager.Register
        public class AuthenticationStateDefinition : StateDefinition<AuthenticationState>
        {
            public override void Define(IStateFromBuilder<AuthenticationState> builder)
            {
                builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
                    .Changing(this, a => a.ChangingHandler1)
                    .Changed(this, a => a.ChangedHandler1);

                builder.OnEntering(AuthenticationState.Authenticated, this, a => a.OnEnteringHandler1);
                builder.OnEntered(AuthenticationState.Authenticated, this, a => a.OnEnteredHandler1);

                builder.OnExiting(AuthenticationState.Unauthenticated, this, a => a.OnExitingHandler1);
                builder.OnExited(AuthenticationState.Authenticated, this, a => a.OnExitedHandler1);

                builder.OnEditing(AuthenticationState.Authenticated, this, a => a.OnEditingHandler1);
                builder.OnEdited(AuthenticationState.Authenticated, this, a => a.OnEditedHandler1);

                builder.ThrowExceptionWhenDiscontinued = true;
            }

            private void ChangingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                var data = changeinfo.DataAs<Dictionary<string, object>>();
                data["key1"] = "ChangingHandler1";
            }

            private void OnEnteringHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                // changeinfo.Continue = false; //this will prevent changing the state
            }

            private void OnEditedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnExitedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnEnteredHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnEditingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
            }

            private void OnExitingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
            }

            private void ChangedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {
            }
        }

        public class AnotherAuthenticationStateDefinition : StateDefinition<AuthenticationState>
        {
            public override void Define(IStateFromBuilder<AuthenticationState> builder)
            {
                builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
                    .Changing(this, a => a.ChangingHandler2);

            }

            private void ChangingHandler2(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                var data = changeinfo.DataAs<Dictionary<string, object>>();
                data["key2"] = "ChangingHandler2";
            }
        }
    }

    public enum AuthenticationState
    {
        Unauthenticated,
        Authenticated
    }
}

0

私のソリューションを使用できます。これが最も便利な方法です。また、無料です。

3つのステップでステートマシンを作成します。

1. ノードエディターでスキームを作成し、 ライブラリを使用してプロジェクトにロードします📚

StateMachine stateMachine = new StateMachine( "scheme.xml");

2.イベントに関するアプリのロジックを説明してください⚡

stateMachine.GetState( "State1")。OnExit(Action1);
stateMachine.GetState( "State2")。OnEntry(Action2);
stateMachine.GetTransition( "Transition1")。OnInvoke(Action3);
stateMachine.OnChangeState(Action4);

3.ステートマシンを実行する🚘

stateMachine.Start();

リンク:

ノードエディター:https : //github.com/SimpleStateMachine/SimpleStateMachineNodeEditor

ライブラリ:https : //github.com/SimpleStateMachine/SimpleStateMachineLibrary

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.