Stateless. A C# Hierarchical State Machine.

Today I finally got some time to research Stateless And you know, it’s awesome!

Key features:

  • Generic support for states and triggers of any .NET type (numbers, strings, enums, etc.)
  • Hierarchical states
  • Entry/exit events for states
  • Guard clauses to support conditional transitions
  • Introspection
  • Ability to store state externally (for example, in a property tracked by Linq to SQL)
  • Parameterised triggers
  • Reentrant states

Let's consider the following example (it's available in the Stateless source code).

Program.cs

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. namespace BugTrackerExample
  7. {
  8.     class Program
  9.     {
  10.         static void Main(string[] args)
  11.         {
  12.             var bug = new Bug("Incorrect stock count");
  13.  
  14.             bug.Assign("Joe");
  15.             bug.Defer();
  16.             bug.Assign("Harry");
  17.             bug.Assign("Fred");
  18.             bug.Close();
  19.  
  20.             Console.ReadKey(false);
  21.         }
  22.     }
  23. }

Bug.cs

  1. using System;</span>
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Stateless;
  6.  
  7. namespace BugTrackerExample
  8. {
  9.     public class Bug
  10.     {
  11.         enum State { Open, Assigned, Deferred, Resolved, Closed }
  12.         enum Trigger { Assign, Defer, Resolve, Close }
  13.  
  14.         State _state = State.Open;
  15.         StateMachine<State, Trigger> _machine;
  16.         StateMachine<State, Trigger>.TriggerWithParameters<string> _assignTrigger;
  17.  
  18.         string _title;
  19.         string _assignee;
  20.  
  21.         public Bug(string title)
  22.         {
  23.             _title = title;
  24.  
  25.             _machine = new StateMachine<State, Trigger>(() => _state, s => _state = s);
  26.  
  27.             _assignTrigger = _machine.SetTriggerParameters<string>(Trigger.Assign);
  28.  
  29.             _machine.Configure(State.Open)
  30.                 .Permit(Trigger.Assign, State.Assigned);
  31.  
  32.             _machine.Configure(State.Assigned)
  33.                 .SubstateOf(State.Open)
  34.                 .OnEntryFrom(_assignTrigger, assignee => OnAssigned(assignee))
  35.                 .PermitReentry(Trigger.Assign)
  36.                 .Permit(Trigger.Close, State.Closed)
  37.                 .Permit(Trigger.Defer, State.Deferred)
  38.                 .OnExit(() => OnDeassigned());
  39.  
  40.             _machine.Configure(State.Deferred)
  41.                 .OnEntry(() => _assignee = null)
  42.                 .Permit(Trigger.Assign, State.Assigned);
  43.         }
  44.  
  45.         public void Close()
  46.         {
  47.             _machine.Fire(Trigger.Close);
  48.         }
  49.  
  50.         public void Assign(string assignee)
  51.         {
  52.             _machine.Fire(_assignTrigger, assignee);
  53.         }
  54.  
  55.         public bool CanAssign
  56.         {
  57.             get
  58.             {
  59.                 return _machine.CanFire(Trigger.Assign);
  60.             }
  61.         }
  62.  
  63.         public void Defer()
  64.         {
  65.             _machine.Fire(Trigger.Defer);
  66.         }
  67.  
  68.         void OnAssigned(string assignee)
  69.         {
  70.             if (_assignee != null && assignee != _assignee)
  71.                 SendEmailToAssignee("Don't forget to help the new guy.");
  72.  
  73.             _assignee = assignee;
  74.             SendEmailToAssignee("You own it.");
  75.         }
  76.  
  77.         void OnDeassigned()
  78.         {
  79.             SendEmailToAssignee("You're off the hook.");
  80.         }
  81.  
  82.         void SendEmailToAssignee(string message)
  83.         {
  84.             Console.WriteLine("{0}, RE {1}: {2}", _assignee, _title, message);
  85.         }
  86.     }
  87. }

* Highlighted with QuickHighlighter.com