State Machines With Enumerations
I have written a simple set of classes (two, actually) that can be used to implement a simple state machine over an enumerated type (enum).
You first apply a custom attribute to individual enumeration values, then you can check if a given transition is allowed or not.
Let's look at the code:
using
System;using
System.Collections.Generic;using
System.Text;namespace StateMachine{
[
Serializable] [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public sealed class StateAttribute: Attribute{
#region Private fields private Enum[] nextStates = null; private Boolean final = false; private Boolean initial = false;#endregion
#region
Public constructors public StateAttribute(Boolean initial, Boolean final, params Object[] nextStates){
Debug.Assert(nextStates != null, "nextStates is null"); List<Enum> list = new List<Enum>(); foreach (Object nextState in nextStates){
Debug.Assert(nextState is Enum, "value is not an enumerated type"); list.Add((Enum)nextState);}
this.Initial = initial; this.Final = final; this.nextStates = list.ToArray();}
public StateAttribute(params Object[] nextStates): this(false, false, nextStates){
}
#endregion
#region
Public properties public Boolean Initial{
get{
return (this.initial);}
set{
this.initial = value;}
}
public Boolean Final{
get{
return (this.final);}
set{
this.final = value;}
}
public Enum[] NextStates{
get{
return (this.nextStates);}
}
#endregion
#region
Public override methods public override Boolean Equals(Object obj){
if (!(obj is StateAttribute)){
return (false);}
if ((Object) this == obj){
return (true);}
StateAttribute other = obj as StateAttribute; if (other.nextStates.Length != this.nextStates.Length){
return (false);}
foreach (Enum e in this.nextStates){
if (Array.IndexOf(other.nextStates, e) < 0){
return (false);}
}
return((this.initial == other.initial) && (this.final == other.final));}
#endregion
}
public static class StateMachine
{
#region Public static methods public static Boolean HasStateMachine(Type enumType){
return (GetStates(enumType).Length != 0);}
public static Enum[] GetInitialStates(Type enumType){
Debug.Assert(enumType != null, "enumType is null"); List<Enum> states = new List<Enum>(); foreach (Enum state in GetStates(enumType)){
FieldInfo fi = enumType.GetField(state.ToString()); Debug.Assert(fi!= null, "Field not found"); StateAttribute s = Attribute.GetCustomAttribute(fi, typeof(StateAttribute)) as StateAttribute; if ((s != null) && (s.Initial == true)){
states.Add(state);
}
}
return (states.ToArray());}
public static Enum[] GetFinalStates(Type enumType){
Debug.Assert(enumType != null, "enumType is null"); List<Enum> states = new List<Enum>(); foreach (Enum state in GetStates(enumType)){
FieldInfo fi = enumType.GetField(state.ToString()); Debug.Assert(fi!= null, "Field is null"); StateAttribute s = Attribute.GetCustomAttribute(fi, typeof(StateAttribute)) as StateAttribute; if ((s != null) && (s.Final == true)){
states.Add(state);
}
}
return (states.ToArray());}
public static Enum[] GetStates(Type enumType){
Debug.Assert(enumType != null, "enumType is null"); Debug.Assert(enumType.IsEnum == true, "enumType is not an enum"); List<Enum> states = new List<Enum>(); foreach (FieldInfo fi in enumType.GetFields()){
StateAttribute s = Attribute.GetCustomAttribute(fi, typeof(StateAttribute)) as StateAttribute; if (s != null){
states.Add((Enum) fi.GetValue(null));}
}
return (states.ToArray());}
public static Boolean CanTransition(Enum initialState, Enum finalState){
Debug.Assert(initialState.GetType() == finalState.GetType(), "states are not of the same type"); return (CanTransition(initialState, finalState, new List<Enum>()));}
public static Enum[] NextStates(Enum state){
FieldInfo fi = state.GetType().GetField(state.ToString());Debug.Assert(fi != null, "Field not found");
StateAttribute s = Attribute.GetCustomAttribute(fi, typeof(StateAttribute)) as StateAttribute; Debug.Assert(s != null, "Attribute not found");List<Enum> nextStates = new List<Enum>();
foreach (Object nextState in s.NextStates){
if (nextState is Enum){
if (nextStates.Contains((Enum) nextState) == false){
nextStates.Add((Enum) nextState);}
}
}
return (nextStates.ToArray());}
public static Enum[] PreviousStates(Enum state){
List<Enum> states = new List<Enum>(); foreach (Enum s in GetStates(state.GetType())){
if (Array.IndexOf(NextStates(s), state) >= 0){
if (states.Contains(s) == false){
states.Add(s);
}
}
}
return(states.ToArray());}
public static Boolean IsFinal(Enum state){
FieldInfo fi = state.GetType().GetField(state.ToString());Debug.Assert(fi != null, "Field not found");
StateAttribute s = Attribute.GetCustomAttribute(fi, typeof(StateAttribute)) as StateAttribute;
return ((s != null) && (s.Final == true));}
public static Boolean IsInitial(Enum state){
FieldInfo fi = state.GetType().GetField(state.ToString());Debug.Assert(fi != null, "Field not found");
StateAttribute s = Attribute.GetCustomAttribute(fi, typeof(StateAttribute)) as StateAttribute;
return ((s != null) && (s.Initial == true));}
#endregion
#region
Private static methods private static Boolean CanTransition(Enum initialState, Enum finalState, List<Enum> processedStates){
foreach (Enum state in NextStates(initialState)){
if (processedStates.Contains(state) == true){
continue;}
processedStates.Add(state);
if (state.Equals(finalState) == true){
return (true);}
return (CanTransition(state, finalState, processedStates));}
return (false);}
#endregion
}
}
And here's a quick sample:
public enum State
{
[State(true, false, B1, B2)]A,
[State(C)]B1,
[State(D1)]B2,
[State(D2)]C,
[State(E)]D1,
[State(E)]D2,
[State(Initial = false, Final = true)]E
}
Boolean t1 = StateMachine.CanTransition(State.A, State.B1); //true Boolean t2 = StateMachine.CanTransition(State.D1, State.A); //false Boolean sm = StateMachine.HasStateMachine(typeof(State)); //true Boolean f = StateMachine.IsFinal(State.E); //true Enum[] initialStates = StateMachine.GetInitialStates(typeof(State)); //A Enum[] secondLevelStates = StateMachine.NextStates(State.A); //B1, B2
Enum
[] finalStates = StateMachine.GetFinalStates(typeof(State)); //D