Ejemplo de máquina de estado simple en C #?

Actualizar:

De nuevo, gracias por los ejemplos, han sido muy útiles y con lo siguiente no quiero quitarles nada.

¿No son los ejemplos dados actualmente, por lo que los entiendo y las máquinas de estados, solo la mitad de lo que generalmente entendemos por una máquina de estado?
En el sentido de que los ejemplos cambian de estado, pero eso solo se representa cambiando el valor de una variable (y permitiendo diferentes cambios de valores en diferentes estados), mientras que generalmente una máquina de estados también debería cambiar su comportamiento, y el comportamiento no (solo) en el sentido de permitir diferentes cambios de valores para una variable dependiendo del estado, pero en el sentido de permitir que se ejecuten diferentes métodos para diferentes estados.

¿O tengo un concepto erróneo de las máquinas de estado y su uso común?

Atentamente


Pregunta original:

Encontré esta discusión sobre máquinas de estados y bloques de iteradores en c # y herramientas para crear máquinas de estado y lo que no para C #, así que encontré muchas cosas abstractas, pero como novato, todo esto es un poco confuso.

Por lo tanto, sería genial si alguien pudiera proporcionar un código fuente de C #, ejemplo que realice una máquina de estado simple con quizás 3,4 estados, solo para entenderlo.


Comencemos con este diagtwig de estado simple:

diagrama de máquina de estado simple

Tenemos:

  • 4 estados (inactivo, activo, en pausa y salido)
  • 5 tipos de transiciones de estado (Comando de inicio, Comando de finalización, Comando de pausa, Comando de reanudación, Comando de salida).

Puede convertir esto a C # de varias maneras, como realizar una instrucción de conmutación en el estado actual y el comando, o buscar transiciones en una tabla de transición. Para esta máquina de estado simple, prefiero una tabla de transición, que es muy fácil de representar usando un 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 transitions; public ProcessState CurrentState { get; private set; } public Process() { CurrentState = ProcessState.Inactive; transitions = new Dictionary { { 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(); } } } 

Como una cuestión de preferencia personal, me gusta diseñar mis máquinas de estado con una función GetNext para devolver el siguiente estado de manera determinista , y una función MoveNext para mutar la máquina de estado.

Es posible que desee utilizar una de las máquinas de estado finito de código abierto existentes. Por ejemplo, bbv.Common.StateMachine se encuentra en http://code.google.com/p/bbvcommon/wiki/StateMachine . Tiene una syntax fluida muy intuitiva y muchas características tales como, acciones de entrada / salida, acciones de transición, guardias, implementación pasiva jerárquica (ejecutada en el hilo del llamante) e implementación activa (propio hilo en el que se ejecuta el fsm, los eventos se agregan a una cola).

Tomando el ejemplo de Juliets, la definición de la máquina de estado es muy fácil:

 var fsm = new PassiveStateMachine(); 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); 

Actualización : la ubicación del proyecto se ha movido a: https://github.com/appccelerate/statemachine

Aquí hay un ejemplo de una máquina de estado finito muy clásica, que modela un dispositivo electrónico muy simplificado (como un televisor)

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

Aquí hay una autoproducción desvergonzada, pero hace un tiempo creé una biblioteca llamada YieldMachine que permite describir una máquina de estados de complejidad limitada de una manera muy simple y clara. Por ejemplo, considere una lámpara:

máquina de estado de una lámpara

Tenga en cuenta que esta máquina de estados tiene 2 activadores y 3 estados. En el código de YieldMachine, escribimos un método único para todos los comportamientos relacionados con el estado, en el cual cometemos la horrible atrocidad de usar goto para cada estado. Un disparador se convierte en una propiedad o campo de tipo Action , decorado con un atributo llamado Trigger . He comentado el código del primer estado y sus transiciones a continuación; los siguientes estados siguen el mismo patrón.

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

Corto y agradable, eh!

Esta máquina de estados se controla simplemente mediante el envío de activadores a ella:

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

Solo para aclarar, he agregado algunos comentarios al primer estado para ayudarlo a entender cómo usar esto.

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

Esto funciona porque el comstackdor de C # realmente creó una máquina de estado internamente para cada método que usa el yield return . Esta construcción generalmente se usa para crear secuencias de datos de forma perezosa, pero en este caso no estamos realmente interesados ​​en la secuencia devuelta (que de todos modos es nula), sino en el comportamiento de estado que se crea debajo del capó.

La clase base StateMachine hace una reflexión sobre la construcción para asignar código a cada acción [Trigger] , que establece el miembro Trigger y mueve la máquina de estados hacia adelante.

Pero realmente no necesita comprender las partes internas para poder usarlo.

Puede codificar un bloque iterador que le permite ejecutar un bloque de código de forma orquestada. Cómo se divide el bloque de códigos realmente no tiene que corresponder a nada, es solo cómo se quiere codificar. Por ejemplo:

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

En este caso, cuando llama a CountToTen, en realidad, nada se ejecuta todavía. Lo que obtiene es efectivamente un generador de máquina de estado, para lo cual puede crear una nueva instancia de la máquina de estado. Para ello, llama a GetEnumerator (). El IEnumerator resultante es efectivamente una máquina de estado que puede manejar llamando a MoveNext (…).

Por lo tanto, en este ejemplo, la primera vez que llame a MoveNext (…) verá “1” escrito en la consola, y la próxima vez que llame a MoveNext (…) verá 2, 3, 4 y luego 5, 6, 7 y luego 8, y luego 9, 10. Como pueden ver, es un mecanismo útil para orquestar cómo deberían ocurrir las cosas.

Estoy publicando otra respuesta aquí ya que se trata de máquinas de estado desde una perspectiva diferente; muy visual.

Mi respuesta original es el código imperítico clásico. Creo que es bastante visual, ya que el código se debe a la matriz que hace que la visualización de la máquina de estado sea simple. El inconveniente es que tienes que escribir todo esto. La respuesta de Remos alivia el esfuerzo de escribir el código de la placa de la caldera, pero es mucho menos visual. Hay una tercera alternativa; realmente dibujando la máquina de estado.

Si usa .NET y puede orientar la versión 4 del tiempo de ejecución, tiene la opción de utilizar las actividades de la máquina de estado del flujo de trabajo . En esencia, estos le permiten dibujar la máquina de estado (como en el diagtwig de Juliet ) y hacer que el tiempo de ejecución WF lo ejecute por usted.

Consulte el artículo de MSDN Building State Machines con Windows Workflow Foundation para obtener más detalles, y este sitio CodePlex para obtener la última versión.

Esa es la opción que siempre preferiría al orientarme a .NET porque es fácil de ver, cambiar y explicar a los que no son progtwigdores; ¡las imágenes valen más que mil palabras!

Es útil recordar que las máquinas de estado son una abstracción y no se necesitan herramientas particulares para crear una, sin embargo, las herramientas pueden ser útiles.

Por ejemplo, puede realizar una máquina de estado con funciones:

 void Hunt(IList gulls) { if (gulls.Empty()) return; var target = gulls.First(); TargetAcquired(target, gulls); } void TargetAcquired(Gull target, IList 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 gulls) { Console.WriteLine("Suck on it {0}!", target.Name); Hunt(gulls); } void TargetMissed(Gull target, IList gulls) { Console.WriteLine("I'll get ya!"); TargetAcquired(target, gulls); } 

Esta máquina podría cazar gaviotas e intentar golpearlas con globos de agua. Si falla, intentará disparar uno hasta que llegue (podría hacer con algunas expectativas realistas;)), de lo contrario se regodeará en la consola. Continúa cazando hasta que queda fuera de las gaviotas para hostigar.

Cada función corresponde a cada estado; los estados de inicio y fin (o aceptación ) no se muestran. Sin embargo, probablemente haya más estados allí que modelados por las funciones. Por ejemplo, después de disparar el globo, la máquina está realmente en otro estado que antes, pero decidí que esta distinción no era práctica.

Una forma común es usar clases para representar estados y luego conectarlos de diferentes maneras.

Todavía no he intentado implementar un FSM en C #, pero todo esto suena (o se ve) muy complicado en la forma en que manejaba los FSM en el pasado en lenguajes de bajo nivel como C o ASM.

Creo que el método que siempre he conocido se llama algo así como un “bucle iterativo”. En él, esencialmente tiene un bucle ‘while’ que sale periódicamente en función de los eventos (interrupciones), luego regresa al bucle principal nuevamente.

Dentro de los controladores de interrupción, pasaría un estado actual y devolvería un estado siguiente, que luego sobrescribirá la variable de estado actual en el ciclo principal. Usted hace esto ad infinitum hasta que el progtwig se cierra (o el microcontrolador se reinicia).

Lo que estoy viendo otras respuestas parecen muy complicadas en comparación con la forma en que, en mi opinión, se pretende implementar un FSM; su belleza radica en su simplicidad y FSM puede ser muy complicado con muchos, muchos estados y transiciones, pero permiten que el proceso complicado se descomponga y se digiera fácilmente.

Me doy cuenta de que mi respuesta no debe incluir otra pregunta, pero me veo obligado a preguntar: ¿por qué estas otras soluciones propuestas parecen ser tan complicadas?
Parecen ser como golpear una uña pequeña con un martillo gigante.

Encontré este gran tutorial en línea y me ayudó a entender las máquinas de estados finitos.

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

El tutorial es independiente del idioma, por lo que se puede adaptar fácilmente a sus necesidades de C #.

Además, el ejemplo utilizado (una ant que busca comida) es fácil de entender.

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

Hoy estoy profundamente en State Design Pattern. Hice y probé ThreadState, que es igual (+/-) a Threading en C #, como se describe en la imagen de Threading in C #

enter image description here

Puede agregar fácilmente nuevos estados, configurar movimientos de un estado a otro es muy fácil porque está encapsulado en la implementación del estado

Implementación y uso en: implementa .NET ThreadState por State Design Pattern

Qué combate StatePattern. ¿Eso se ajusta a tus necesidades?

Creo que su contexto está relacionado, pero vale la pena intentarlo.

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

Esto le permite a sus estados decidir a dónde ir y no la clase “objeto”.

Bruno

Acabo de contribuir con esto:

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

Este es uno de los ejemplos que muestra el envío directo e indirecto de comandos, con estados como IObserver (de señal), por lo tanto, respondedores a una fuente de señal, IObservable (de señal):

 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 { // 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 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 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 and IObservable> : var remote = new SignalSource(); // 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(); } } } 

Nota: este ejemplo es bastante artificial y está destinado principalmente a la demostración de varias características ortogonales. There should seldomly be a real need to implement the state value domain itself by a full blown class, using the CRTP ( see : http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern ) like this.

Here’s for a certainly simpler and likely much more common implementation use case (using a simple enum type as the states value domain), for the same state machine, and with the same test case :

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 is a shortcut for / derived from State, // which in turn is a shortcut for / derived from State : public class Device : State { // 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 made this generic state machine out of Juliet’s code. It’s working awesome for me.

These are the benefits:

  • you can create new state machine in code with two enums TState and TCommand ,
  • added struct TransitionResult to have more control over the output results of [Try]GetNext() methods
  • exposing nested class StateTransition only through AddTransition(TState, TCommand, TState) making it easier to work with it

Código:

 public class StateMachine where TState : struct, IConvertible, IComparable where TCommand : struct, IConvertible, IComparable { protected class StateTransition 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 other = obj as StateTransition; return other != null && this.CurrentState.CompareTo(other.CurrentState) == 0 && this.Command.CompareTo(other.Command) == 0; } } private Dictionary, 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, TState>(); } ///  /// Defines a new transition inside this state machine ///  /// source state /// transition condition /// destination state protected void AddTransition(TState start, TCommand command, TState end) { transitions.Add(new StateTransition(start, command), end); } public TransitionResult TryGetNext(TCommand command) { StateTransition transition = new StateTransition(CurrentState, command); TState nextState; if (transitions.TryGetValue(transition, out nextState)) return new TransitionResult(nextState, true); else return new TransitionResult(CurrentState, false); } public TransitionResult 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 { public TransitionResult(TState newState, bool isValid) { NewState = newState; IsValid = isValid; } public TState NewState; public bool IsValid; } 

Cómo utilizar:

This is how you can create a OnlineDiscountStateMachine from the generic class:

Define an enum OnlineDiscountState for its states and an enum OnlineDiscountCommand for its commands.

Define a class OnlineDiscountStateMachine derived from the generic class using those two enums

Derive the constructor from base(OnlineDiscountState.InitialState) so that the initial state is set to OnlineDiscountState.InitialState

Use AddTransition as many times as needed

 public class OnlineDiscountStateMachine : StateMachine { 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); } } 

use the derived state machine

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

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.

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

Alex

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( "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( stopped, running, ( state, command ) => command.Equals( "play" ) ); player.CreateTransition( 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.

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.