للمبتدئين - مشاريع جاهزة بلغة c#




سبيل المثال آلة بسيطة في C#؟ (12)

اليوم أنا في عمق نمط تصميم الدولة. فعلت واختبرت ThreadState ، والتي تساوي (+/-) إلى خيوط في C # ، كما هو موضح في الصورة من وصف رابط الدخول هنا

يمكنك بسهولة إضافة حالات جديدة ، وتكوين التحركات من حالة واحدة إلى أخرى من السهل جدا لأنها أغلقت في تنفيذ الدولة

تنفيذ واستخدام في: تنفذ NET ThreadState بواسطة نمط تصميم الدولة

تحديث:

شكرا مرة أخرى على الأمثلة ، فقد كانت مفيدة للغاية ومع ما يلي لا أقصد اتخاذ أي شيء بعيدا عنهم.

أليست الأمثلة المعطاة حاليا ، بقدر ما أفهمها وآلات الدولة ، فقط نصف ما نفهمه عادة من قبل آلة الدولة؟
بمعنى أن الأمثلة تغير الحالة ولكنها لا تمثل إلا بتغيير قيمة متغير (وتسمح بتغييرات مختلفة في القيمة في حالات مختلفة) ، في حين أن جهاز الحالة عادةً يجب أن يغيّر سلوكه أيضًا ، والسلوك ليس (فقط) في بمعنى السماح بتغييرات مختلفة في القيمة لمتغير اعتمادًا على الحالة ، ولكن بمعنى السماح بتنفيذ طرق مختلفة لحالات مختلفة.

أو هل لدي فكرة خاطئة عن أجهزة الدولة واستخدامها الشائع؟

تحياتي الحارة

السؤال الأصلي:

لقد وجدت هذا النقاش حول أجهزة الدولة ومجموعات الكتل الموسيقية في c # والأدوات لإنشاء أجهزة الحالة وما ليس لـ C # ، لذلك وجدت الكثير من الأشياء المجردة ولكن كمسؤول عن ذلك ، فإن كل هذا مثير للارتباك قليلاً.

لذلك سيكون من الرائع أن يقدم شخص ما مثالًا عن التعليمات البرمجية لمصدر C # يحقق جهازًا حكوميًا بسيطًا مع ما لا يقل عن 3،4 ولاية ، وذلك للحصول على جوهره.


ايم نشر annother الجواب هنا لأن هذا هو آلات الدولة من منظور مختلف. بصري جدا.

اجابتي الاصلية هي كود غير عادي clasic. أعتقد أن بصري تماما كما رمز يذهب لأنه من الصفيف الذي يجعل تصور جهاز الدولة بسيطة. الجانب السلبي هو أن تكتب كل هذا. تخفف إجابة Remos من مجهود كتابة رمز الغلاية ، ولكنها أقل بروزًا. هناك البديل الثالث. حقا رسم آلة الدولة.

إذا كنت تستخدم .NET ويمكن أن تستهدف الإصدار 4 من وقت التشغيل ، فحينئذٍ يكون لديك خيار استخدام أنشطة جهاز حالة سير العمل . هذه في جوهرها تمكنك من رسم جهاز الدولة (كما هو الحال في الرسم البياني لجولييت) وجعل وقت التشغيل WF ينفذها لك.

راجع مقالة MSDN Building State Machines مع Windows Workflow Foundation لمزيد من التفاصيل ، وهذا الموقع CodePlex للحصول على أحدث إصدار.

هذا هو الخيار الذي أفضل دائمًا عند استهداف .NET لأنه يسهل رؤيته وتغييره وشرحه لغير المبرمجين ؛ الصور تستحق ألف كلمة كما يقولون!


قد ترغب في استخدام واحدة من أجهزة الدولة الحالية المفتوحة المصدر. على سبيل المثال ، تم العثور على bbv.Common.StateMachine على http://code.google.com/p/bbvcommon/wiki/StateMachine . يحتوي على بنية بديهية غاية في السهولة والكثير من الميزات مثل ، إجراءات الدخول / الخروج ، إجراءات النقل ، الحراس ، التراتبية ، التنفيذ السلبي (تم تنفيذها على مؤشر المتصل) والتنفيذ النشط (الخيط الخاص الذي يعمل عليه fsm ، تتم إضافة الأحداث إلى قائمة انتظار).

مع أخذ Juliets على سبيل المثال ، يصبح تعريف الآلة الحكومية أمرًا سهلاً للغاية:

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


لقد ساهمت بهذا للتو:

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

فيما يلي أحد الأمثلة على إعادة توجيه الأوامر المباشرة وغير المباشرة للأوامر ، مع الحالات كـ IObserver (للإشارة) ، وبالتالي المستجيبين لمصدر إشارة ، IObservable (للإشارة):

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


لنبدأ بهذا الشكل البسيط للحالة:

لدينا:

  • 4 حالات (غير نشط ، نشط ، متوقف مؤقتًا ، ومغلق)
  • 5 أنواع من التحولات الولائية (Begin Command، End Command، Pause Command، Resume Command، Exit Command).

يمكنك تحويل هذا إلى 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 لإرجاع الحالة التالية بشكل deterministically ، ووظيفة MoveNext لتحول جهاز الحالة.


من المفيد أن تتذكر أن أجهزة الحالة هي تجريد ، ولا تحتاج إلى أدوات معينة لإنشاء واحدة ، إلا أن الأدوات قد تكون مفيدة.

يمكنك على سبيل المثال تحقيق جهاز الحالة مع وظائف:

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);
}

ستقوم هذه الآلة بمطاردة النوارس ومحاولة ضربهم بالونات الماء. إذا فاتته فإنه سيحاول إطلاق واحدة حتى يضرب (يمكن أن تفعل مع بعض التوقعات الواقعية ؛)) ، وإلا فإنه سوف ينثر في وحدة التحكم. تواصل الصيد حتى يتم التخلص من النوارس.

كل وظيفة تتوافق مع كل حالة ؛ لا تظهر حالات البداية والنهاية (أو القبول ). هناك على الارجح المزيد من الدول هناك من طراز من خلال وظائف على الرغم من. على سبيل المثال بعد إطلاق البالون ، تكون الآلة بالفعل في حالة أخرى مما كانت عليه قبل ذلك ، لكنني قررت أن هذا التمييز غير عملي.

الطريقة الشائعة هي استخدام الفئات لتمثيل الحالات ، ثم توصيلها بطرق مختلفة.


وجدت هذا البرنامج التعليمي العظيم على الانترنت وساعدني على التفاف رأسي حول آلات الحالة المحدودة.

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();
    }

    (...)
}

يا له من ولاية StatePattern. هل هذا مناسب لاحتياجاتك؟

أعتقد أن سياقها مرتبط ، ولكن قيمته تسديدة بالتأكيد.

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

هذا يسمح لولاياتك بتحديد مكان الذهاب وليس فئة "الكائن".

برونو


FiniteStateMachine is a Simple State Machine, written in C# Link

Advantages tu use my library FiniteStateMachine:

  1. Define a "context" class to present a single interface to the outside world.
  2. Define a State abstract base class.
  3. Represent the different "states" of the state machine as derived classes of the State base class.
  4. Define state-specific behavior in the appropriate State derived classes.
  5. Maintain a pointer to the current "state" in the "context" class.
  6. To change the state of the state machine, change the current "state" pointer.

Download DLL Download

Example on 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;


     }

 }

I found Juliet's answer quite simple and flawless so I made a few changes such as :

  • making the state machine Generic (working with TState and TCommand enums)
  • using struct TransitionResult<TState> type as the output results of [Try]GetNext() methods so that you can check if the transition is valid or not without throwing exception.
  • hiding nested class StateTransition from whoever working with the state machine, and added AddTransition(TState, TCommand, TState) method instead.

Here is the code to my generic version of her state machine:

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;
    }
}

This is the return type of TryGetNext method:

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

deriving a state machine from the generic state machine:

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);
    }
}

using the derived state machine

    odsm = new OnlineDiscountStateMachine();
    public void Connect()
    {
        var result = odsm.TryGetNext(OnlineDiscountCommand.Connect);
        if (!result.IsValid)
            throw new Exception("invalid state");
        else if(result.NewState == OnlineDiscountState.Error_AuthenticationError)
            throw new Exception("invalid user/pass");
    }

I would recommend state.cs . I personally used state.js (the JavaScript version) and am very happy with it. That C# version works in a similar way.

You instantiate states:

        // 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" );
        ...

You instantiate some transitions:

        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" ) );

You define actions on states and transitions:

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

And that's (pretty much) it. Look at the website for more information.


In my opinion a state machine is not only meant for changing states but also (very important) for handling triggers/events within a specific state. If you want to understand state machine design pattern better, a good description can be found within the book Head First Design Patterns, page 320 .

It is not only about the states within variables but also about handling triggers within the different states. Great chapter (and no, there is no fee for me in mentioning this :-) which contains just an easy to understand explanation.





state-machines