首页 文章

C#中的简单状态机示例?

提问于
浏览
219

Update:

再次感谢这些例子,他们非常有帮助,以下我并不是要从他们那里拿走任何东西 .

就我理解它们和状态机而言,目前给出的例子不是我们通常理解的状态机的一半吗?
在某种意义上,示例确实改变了状态,但是行为不是(仅)在允许变量取决于状态的情况下对变量进行不同的值更改,而是允许为不同的状态执行不同的方法 .

或者我对状态机及其常见用途存在误解?

最好的祝福


Original question:

我发现这个关于state machines & iterator blocks in c#的讨论和创建状态机的工具以及C#没有的东西,所以我发现了很多抽象的东西,但作为一个菜鸟所有这一切都有点令人困惑 .

因此,如果有人能够提供一个C#源代码示例,它可以实现一个简单的状态机,可能有3,4个状态,只是为了得到它的要点 .


18 回答

  • 8

    FiniteStateMachine是一个简单的状态机,用C#Link编写

    优点tu使用我的库FiniteStateMachine:

    • 定义"context"类以向外界呈现单个接口 .

    • 定义状态抽象基类 .

    • 将状态机的不同"states"表示为State基类的派生类 .

    • 在适当的State派生类中定义特定于州的行为 .

    • 保持指向"context"类中当前"state"的指针 .

    • 要更改状态机的状态,请更改当前的"state"指针 .

    下载DLL Download

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

    让我们从这个简单的状态图开始:

    simple state machine diagram

    我们有:

    • 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 函数设计我的状态机以返回下一个状态deterministically,并使用 MoveNext 函数来改变状态机 .

  • 7

    您可能希望使用现有的开源有限状态机之一 . 例如 . 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);
    

    Update :项目位置已移至:https://github.com/appccelerate/statemachine

  • 68

    这是一个非常经典的有限状态机的例子,建模一个非常简化的电子设备(如电视)

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

    这里有一些无耻的自我宣传,但不久前我创建了一个名为YieldMachine的库,它允许以非常简洁的方式描述有限复杂度的状态机 . 例如,考虑一盏灯:

    state machine of a lamp

    请注意,此状态机有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 成员并向前移动状态机 .

    但是你真的不需要理解内部能够使用它 .

  • 3

    您可以编写迭代器块代码,以便以协调的方式执行代码块 . 代码块如何分解实际上不必与任何东西相对应,它只是你想要编码它的方式 . 例如:

    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 . 正如你所看到的,它是一种有用的机制,用于协调事情应该如何发生 .

  • 0

    我在这里发布了另一个答案,因为这是来自不同角度的状态机;非常直观 .

    我的原始答案是clasic不完整的代码 . 我认为它的代码非常直观,因为数组使得状态机的可视化变得简单 . 缺点是你必须写下这一切 . Remos的答案缓解了编写样板代码的努力,但远没有那么直观 . 还有第三种选择;真的画国家机器 .

    如果您使用的是.NET并且可以定位运行时的第4版,那么您可以选择使用 workflow's state machine activities . 这些实质上是让你绘制状态机(就像在Juliet的图中一样)并让WF运行时为你执行它 .

    有关更多详细信息,请参阅MSDN文章Building State Machines with Windows Workflow Foundation,有关最新版本,请参阅this CodePlex site .

    这是我在定位.NET时总是喜欢的选项,因为它易于查看,更改并向非程序员解释;他们说的图片胜过千言万语!

  • 5

    记住状态机是一个抽象是很有用的,你不需要特殊的工具来创建一个,但是工具可能很有用 .

    例如,您可以实现具有以下功能的状态机:

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

    这台机器会搜寻海鸥并试图用水气球打它们 . 如果它错过它会尝试射击直到它击中(可以做一些现实的期望;)),否则它将在控制台中幸灾乐祸 . 它继续捕猎,直到它被海鸥骚扰 .

    每个功能对应于每个状态;未显示开始和结束(或接受)状态 . 可能有更多的状态,而不是函数建模 . 例如,在发射气球之后,机器实际上处于与之前相比的另一种状态,但我认为这种区别是不切实际的 .

    一种常见的方法是使用类来表示状态,然后以不同的方式连接它们 .

  • 5

    我还没有尝试在C#中实现FSM,但这些听起来(或看起来)非常复杂,因为我过去在C或ASM等低级语言中处理FSM的方式 .

    我相信我一直都知道的方法叫做“迭代循环” . 在它中,你基本上有一个'while'循环,它根据事件(中断)定期退出,然后再次返回主循环 .

    在中断处理程序中,您将传递一个CurrentState并返回一个NextState,然后在主循环中覆盖CurrentState变量 . 无限制地执行此操作直到程序关闭(或微控制器重置) .

    我所看到的其他答案看起来都非常复杂,而在我看来,FSM是如何实现的;它的美丽在于它的简洁性和FSM可能非常复杂,有许多很多状态和转换,但它们允许复杂的过程容易被分解和消化 .

    我意识到我的回答不应该包括另一个问题,但我不得不问:为什么这些其他提议的解决方案看起来如此复杂?
    它们似乎类似于用巨大的大锤击打小钉子 .

  • 2

    在网上找到了这个很棒的教程,它帮助我绕过有限状态机 .

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

    本教程与语言无关,因此可以轻松地适应您的C#需求 .

    此外,使用的例子(寻找食物的 Ant )很容易理解 .

    From the tutorial:

    enter image description here

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

    今天我深入国家设计模式 . 我做了并测试了ThreadState,它等于(/ - )在C#中的线程,如Threading in C#中的图片所述

    enter image description here

    您可以轻松地添加新状态,配置从一个状态到另一个状态的移动非常容易,因为它在状态实现中被封装

    实施和使用于:Implements .NET ThreadState by State Design Pattern

  • 2

    什么回合StatePattern . 这符合您的需求吗?

    我认为它的背景相关,但它确实值得一试 .

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

    这让你的州决定去哪里,而不是“对象”类 .

    布鲁诺

  • 373

    我刚刚贡献了这个:

    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

  • 0

    在我看来,状态机不仅用于改变状态,而且(非常重要)用于处理特定状态内的触发器/事件 . 如果你想更好地理解状态机设计模式,可以在书中找到一个很好的描述Head First Design Patterns, page 320 .

    它不仅涉及变量中的状态,还涉及处理不同状态内的触发器 . 伟大的章节(不,我提到这个:-)没有任何费用,其中只包含一个易于理解的内容说明 .

  • 0

    我让朱丽叶's code. It'对我来说真是太棒了 .

    这些是好处:

    • 你可以在代码中用两个枚举 TStateTCommand 创建新的状态机,

    • 添加了struct TransitionResult<TState> 以更好地控制 [Try]GetNext() 方法的输出结果

    • 暴露嵌套类 StateTransition onlyAddTransition(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

    base(OnlineDiscountState.InitialState) 派生构造函数,以便 initial state 设置为 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);
        }
    
  • 5

    我认为Juliet提出的状态机有一个错误:方法GetHashCode可以为两个不同的转换返回相同的哈希码,例如:

    State = Active(1),Command = Pause(2)=> HashCode = 17 31 62 = 110 State = Paused(2),Command = End(1)=> HashCode = 17 62 31 = 110

    为避免此错误,该方法应如下所示:

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

    亚历克斯

  • 3

    我会推荐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

    NuGet中有2个流行的状态机包 .

    Appccelerate.StateMachine(13.6K下载3.82K的旧版本(bbv.Common.StateMachine))

    StateMachineToolkit(下载1.56K)

    Appccelerate lib有good documentation,但它不支持.NET 4,所以我为我的项目选择了StateMachineToolkit .

相关问题