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

更新:

再次感谢这些例子,他们非常有帮助,下面我不打算带走任何东西。

就我所了解的状态机而言,目前的例子并不是我们通常只能理解状态机的一半吗?
在这个意义上,这些例子确实改变了状态,但是这只是通过改变一个variables的值来表示的(并且允许不同状态下的不同的值改变),而通常状态机也应该改变它的行为,行为不(仅)允许取决于状态的variables允许不同的值变化的意义,但是允许针对不同的状态执行不同的方法的意义。

还是我有一个国家机器的误解和通用?

最好的祝福


原始问题:

我发现这个关于C#中状态机和迭代器块的讨论,以及创build状态机的工具,而不是C#,所以我发现了很多抽象的东西,但作为一个noob所有这些都有点混乱。

所以如果有人能提供一个C#源代码的例子,那么实现一个简单的状态机可能只有3,4个状态,这样才能得到它的要点。


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

简单的状态机图

我们有:

  • 4个状态(非活动,活动,暂停和退出)
  • 5种types的状态转换(开始命令,结束命令,暂停命令,恢复命令,退出命令)。

您可以通过很多方式将其转换为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函数来devise我的状态机,以确定性地返回下一个状态,并使用MoveNext函数来改变状态机。

您可能需要使用现有的开源Finite State Machines之一。 例如在http://code.google.com/p/bbvcommon/wiki/StateMachinefindbbv.Common.StateMachine。; 它具有非常直观的stream畅语法和许多特性,如进入/退出动作,转换动作,警卫,分层,被动执行(在调用者的线程上执行)和主动执行(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

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

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

这里有一些无耻的自我宣传,不久前我创build了一个名为YieldMachine的库,它允许使用一种非常简单的方式来描述复杂度有限的状态机。 例如,考虑一个灯:

灯的状态机

注意这个状态机有2个触发器和3个状态。 在YieldMachine代码中,我们为所有与状态有关的行为编写了一个单一的方法,在这个方法中,我们为每个状态提供了使用goto的可怕的暴行。 触发器成为Actiontypes的属性或字段,并使用名为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方法创build一个状态机。 这个结构通常用来懒散地创build数据序列,但是在这种情况下,我们实际上并不关心返回的序列(无论如何都是空的),而是在引擎盖下创build的状态行为。

StateMachine基类在构造上做了一些反思,将代码分配给每个[Trigger]动作,它设置Trigger成员并向前移动状态机。

但是,你并不需要理解内部使用它。

您可以编写一个迭代器块,让您以协调的方式执行代码块。 代码块如何分解真的不需要对应任何东西,只是你想如何编写代码块。 例如:

 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时,实际上并没有执行任何操作。 你得到的是一个有效的状态机生成器,为此你可以创build一个新的状态机实例。 你可以通过调用GetEnumerator()来做到这一点。 生成的IEnumerator实际上是一个状态机,可以通过调用MoveNext(…)来驱动。

因此,在本例中,您第一次调用MoveNext(…)时,将看到写入控制台的“1”,下一次您调用MoveNext(…)时,将会看到2,3,4和然后是5,6,7和8,然后是9,10。正如你所看到的,这是一个协调事情发生的有用机制。

记住状态机是一个抽象是有用的,你不需要特殊的工具来创build一个,但是工具是有用的。

你可以例如实现一个具有function的状态机:

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

这台机器将寻找海鸥,并尝试用水气球击中他们。 如果错过了,它会尝试发射一个,直到它命中(可以做一些现实的期望;)),否则它会在控制台中变得滑稽。 它继续追捕,直到它没有海鸥骚扰。

每个function对应于每个状态; 不显示开始和结束(或接受 )状态。 那里的状态可能比function模拟的更多。 例如,在发射气球之后,机器实际上处于另一种状态,但是我认为这种区分是不切实际的。

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

我在这里发表了别的答案,因为这是从不同的angular度来看状态机; 非常视觉。

我origianl的答案是clasic不具有代表性的。 我认为它的代码相当可视,因为使状态机可视化简单。 缺点是你必须写这一切。 Remos的回答不过是编写锅炉代码的努力,但远不如视觉效果好。 有第三种select; 真的是在绘制状态机。

如果您正在使用.NET,并且可以定位运行时的版本4,那么您可以select使用工作stream的状态机活动 。 这些本质上可以让你绘制状态机(就像在Juliet的图中一样),让WF运行时为你执行。

有关更多详细信息,请参阅MSDN文章使用Windows Workflow Foundation构build状态机 ,以及此CodePlex网站的最新版本。

这就是我select.NET时的select,因为它很容易看到,改变和解释给非程序员; 他们说,图片胜过千言万语!

我还没有尝试过在C#中实现FSM,但是这些都听起来(或者看起来)非常复杂,就像我过去在C或ASM这样的低级语言中处理FSM的方式一样。

我相信我总是知道的方法被称为“迭代循环”。 在这里,你基本上有一个'while'循环,它会根据事件(中断)周期性地退出,然后再次返回到主循环。

在中断处理程序中,您将传递一个CurrentState并返回一个NextState,然后覆盖主循环中的CurrentStatevariables。 直到程序closures(或者微控制器复位),你才能无限次地进行这个操作。

在我看来,我所看到的其他答案都显得非常复杂,相比之下,FSM是如何实施的; 它的优点在于它的简单性,FSM可以很多很多的状态和转换,但它们使复杂的过程容易被分解和消化。

我意识到我的回答不应该包括另一个问题,但我不得不问:为什么这些其他提出的解决scheme似乎是如此复杂?
它们好像是用巨大的大锤撞击一个小钉子。

什么回合StatePattern。 这是否符合您的需求?

我认为它的背景相关,但值得一试。

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

这让你的状态决定去哪里,而不是“对象”类。

布鲁诺

今天我深入的国家devise模式。 我做了并testing了ThreadState,它等于(+/-)到C#中的Threading,如图所示,在这里input链接描述

在这里输入图像描述

你可以很容易地添加新的状态,configuration从一个状态到另一个状态是非常容易的,因为它在状态实现中被封装

在以下位置实现和使用: 按状态devise模式实现.NET ThreadState

在线发现了这个伟大的教程,它帮助我将自己的头围绕在有限状态机上。

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

该教程是语言不可知的,所以它可以很容易地适应您的C#需求。

另外,使用的例子(寻找食物的ant)很容易理解。

从教程:

在这里输入图像描述

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

我刚刚贡献了这个:

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 // (ie, 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 // (ie, (step equals ExecutionStep.LeaveState) implies (not state.IsFinal)) System.Diagnostics.Debug.Assert((step != ExecutionStep.LeaveState) || !state.IsFinal); // Holds in instance (ie, 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, aka "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(); } } } 

注意:这个例子是相当人造的,主要是为了演示一些正交的特征。 应该很less真正需要通过一个完整的类来实现状态值域本身,像这样使用CRTP(见http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern )。

这是一个更简单,更普遍的实现用例(使用简单枚举types作为状态值域),对于同一个状态机,以及相同的testing用例:

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

I think the state machine proposed by Juliet has a mistake: the method GetHashCode can return the same hash code for two different transitions, for example:

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

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

To avoid this error, the method should be like this:

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

亚历克斯

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 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.

There are 2 popular state machine packages in NuGet.

Appccelerate.StateMachine (13.6K downloads + 3.82K of legacy version (bbv.Common.StateMachine))

StateMachineToolkit (1.56K downloads)

The Appccelerate lib has good documentation , but it does not support .NET 4, so I chose StateMachineToolkit for my project.

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