Stateless 3.0 - A State Machine library for .NET Core
State Machines and business processes that describe a series of states seem like they'll be easy to code but you'll eventually regret trying to do it yourself. Sure, you'll start with a boolean, then two, then you'll need to manage three states and there will be an invalid state to avoid then you'll just consider quitting all together. ;)
"Stateless" is a simple library for creating state machines in C# code. It's recently been updated to support .NET Core 1.0. They achieved this not by targeting .NET Core but by writing to the .NET Standard. Just like API levels in Android abstract away the many underlying versions of Android, .NET Standard is a set of APIs that all .NET platforms have to implement. Even better, the folks who wrote Stateless 3.0 targeted .NET Standard 1.0, which is the broadest and most compatible standard - it basically works everywhere and is portable across the .NET Framework on Windows, .NET Core on Windows, Mac, and LInux, as well as Windows Store apps and all phones.
Sure, there's Windows Workflow, but it may be overkill for some projects. In Nicholas Blumhardt's words:
...over time, the logic that decided which actions were allowed in each state, and what the state resulting from an action should be, grew into a tangle of
if
andswitch
. Inspired by Simple State Machine, I eventually refactored this out into a little state machine class that was configured declaratively: in this state, allow this trigger, transition to this other state, and so-on.
You can use state machines for anything. You can certainly describe high-level business state machines, but you can also easily model IoT device state, user interfaces, and more.
Even better, Stateless also serialize your state machine to a standard text-based "DOT Graph" format that can then be generated into an SVG or PNG like this with http://www.webgraphviz.com. It's super nice to be able to visualize state machines at runtime.
Modeling a Simple State Machine with Stateless
Let's look at a few code examples. You start by describing some finite states as an enum, and some finite "triggers" that cause a state to change. Like a switch could have On and Off as states and Toggle as a trigger.
A more useful example is the Bug Tracker included in the Stateless source on GitHub. To start with here are the states of a Bug and the Triggers that cause state to change:
enum State { Open, Assigned, Deferred, Resolved, Closed }
enum Trigger { Assign, Defer, Resolve, Close }
You then have your initial state, define your StateMachine, and if you like, you can pass Parameters when a state is trigger. For example, if a Bug is triggered with Assign you can pass in "Scott" so the bug goes into the Assigned state - assigned to Scott.
State _state = State.Open;
StateMachine<State, Trigger> _machine;
StateMachine<State, Trigger>.TriggerWithParameters<string> _assignTrigger;
string _title;
string _assignee;
Then, in this example, the Bug constructor describes the state machine using a fluent interface that reads rather nicely.
public Bug(string title)
{
_title = title;
_machine = new StateMachine<State, Trigger>(() => _state, s => _state = s);
_assignTrigger = _machine.SetTriggerParameters<string>(Trigger.Assign);
_machine.Configure(State.Open)
.Permit(Trigger.Assign, State.Assigned);
_machine.Configure(State.Assigned)
.SubstateOf(State.Open)
.OnEntryFrom(_assignTrigger, assignee => OnAssigned(assignee))
.PermitReentry(Trigger.Assign)
.Permit(Trigger.Close, State.Closed)
.Permit(Trigger.Defer, State.Deferred)
.OnExit(() => OnDeassigned());
_machine.Configure(State.Deferred)
.OnEntry(() => _assignee = null)
.Permit(Trigger.Assign, State.Assigned);
}
For example, when the State is Open, it can be Assigned. But as this is written (you can change it) you can't close a Bug that is Open but not Assigned. Make sense?
When the Bug is Assigned, you can Close it, Defer it, or Assign it again. That's PermitReentry(). Also, notice that Assigned is a Substate of Open.
You can have events that are fired as states change. Those events can take actions as you like.
void OnAssigned(string assignee)
{
if (_assignee != null && assignee != _assignee)
SendEmailToAssignee("Don't forget to help the new employee.");
_assignee = assignee;
SendEmailToAssignee("You own it.");
}
void OnDeassigned()
{
SendEmailToAssignee("You're off the hook.");
}
void SendEmailToAssignee(string message)
{
Console.WriteLine("{0}, RE {1}: {2}", _assignee, _title, message);
}
With a nice State Machine library like Stateless you can quickly model states that you'd ordinarily do with a "big ol' switch statement."
What have you used for state machines like this in your projects?
Sponsor: Big thanks to Telerik! They recently published a comprehensive whitepaper on The State of C#, discussing the history of C#, what’s new in C# 7 and whether C# is still a viable language. Check it out!
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
But it's good to know what's on the market and that more and more of these libraries are migrating to netstandard.
One thing my T4 template is missing compared to this is parameterised triggers. It doesn't have DOT graph output either, though that would be really easy to add.
The T4 might have the edge for when there are design changes to the states and transitions, as a lot of boiler-plate code will be rewritten automatically, whereas it looks like you'd have to do more of that by hand with the fluent API, but the T4 can't take care of it all and manual changes would still be required.
Fails though.
Beyond ~50 states, one does not model states at the object level.
One models large macro level states first as a macro level state engine, then decomposes each of the macro level states into many, many micro level states.
Each macro level state has a few states that transition into it and a few states transitioned to from the macro level state.
One models the X inputs/events in the system as a whole, then builds a table for each state/input set combination with the action to take and next state to transition to.
This is well known from at least the mid 1960s. Refer to Wikipedia for Decision Table, filetab or table based programming languages.
The failure of listing states inside the object as opposed to a tabular (input value combination, current state, action to take, next state) approach does not let one validate that the states
- Meet business rules / follow requirements - business users can read a table but not C# code
- Each state and possible combination of inputs has an action and next state -> No un-handled input combinations or states
The table is considerably smaller than the possible input combinations as most of the entries in the table will be "don't care" or "not possible."
Using a macro level states in one table for macro level operations; and an individual table for the micro level states in a particular macro level state greatly simplifies the complexity.
Modularity is achieved in that a macro level state encapsulates most of the content/settings/values/logic needed by its internal micro level states.
Developer test case:
- Extract the business rules/logic from c# code for a project using the state engine mentioned in this blog post.
Consider the time/cost needed to extract and present to business users the states/business logic behind a system using the state engine library with 500+ objects and 2000+ states.
Developed in C, with almost no pointers, internal to my process (not CRT) memory handling (think bounds checking) and stack checking
C++/OO was ruled out for memory handling and slower performance.
Please suggest an alternate design principle.
Real time asynchronous systems suited to state engines to prove all input combinations handled for each state.
Real-time inside the OS address space means you are on your own and cannot drop in any random third party library.
A top level 15 states where each top level state has 20 states is manageable.
This to me is the fundamental limitation of Stateless and all code-based SM definitions. SM inherently lend themselves to diagrams, and the only reason tools do not exist to model them as such is because building such tools is bloody hard! Many attempts have been made over the decadess, and there is even one for Stateless (statelessdesigner.codeplex.com), but they almost always fall short.
But recently we have discovered an open-source product called YAKINDU Statecharts (www.statecharts.org), which I believe is the first visual-modelling tool I have looked at which is impressive, mature, documented, and under active development. It allows you to simulate execution of the state machines (amazing feature!) which really helps in testing and reasoning about your state machines. To use the state machine you then generate code in your desired language. I think the pro edition even includes a unit testing feature (Which, if you have ever tried to unit test a Stateless SM, will know it can be very boring activity - not suited to a procedural language like C#).
The only problem with YAKINDU is lack of C# support. But this feature is currently in development, and if the .NET community showed interest they might push this up their priorities. They take pull requests and am sure they would love to hear from this community.
Anyway, check out YAKINDU - I think it would be an awesome tool to add to the .NET arsenal.
I wrote a visual, lightweight FSM in C# where state interactions are associated with function calls via drag & drop and the state at any time is immutable. It was fun and allowed for a sandboxed FSM, which acts like a state, whose innards are sealed but can be accessed like any other state. The objective was to write a framework for medical devices that would allow separate sensor/action components to be FDA certified allowing to create an end assembly of these components with a faster track to FDA certification...more work intensive that building a device by itself.
So the compile will fail with a type mismatch error if you try to do something like
Bug.close (Bug.open 1)
Here is a sample in F#, it can probably be ported to C# without too much hassle:
(* We want to capture states at the type level. *)
module State =
type open = Open
type assigned = Assigned of string
type deferred = Deferred
type resolved = Resolved
type closed = Closed
module Bug =
(*
We make the record implementation private so users can't just create
bugs in any state they like.
*)
type 'state t = private { id' : int; state : 'state }
let open (id' : int) : State.open t =
{ id' = id'; state = State.Open }
let assign (t : State.open t) (to : string) : State.assigned t =
{ t with state = State.Assigned to }
let close (t : State.assigned t) : State.closed t =
{ t with state = State.Closed }
let defer (t : State.assigned t) : State.deferred t =
{ t with state = State.Deferred }
let reassign (t : State.assigned t) (to : string) : State.assigned t =
{ t with state = State.Assigned to }
Apologies for the weird formatting, not sure why it's doing that.
A benchmark is to write a sliding window packet based file transmission protocol and prove that it handles all scenarios of timeout, bad packets, out of order packets, and disconnect at any time. Try this with while loops, if/then statements first, get bogged down, back up and then create a state diagram; and finally convert the state diagram to a table.
Hint, collapse a few of the adjacent states into very simple if/then logic.
Am I reading it correctly that "SubstateOf" is intended for such hierarchical purposes?
Look back to 1970 and see if you can find the paper by A. C. Day which explained the technique.
Mike L
Implementing the behaviour I need when someone logs on to a website...
InState(UnknownUserState)
.OnEntry()
.Then((state, evnt) =>
{
state.ResetUserLoginDetails();
});
InState(MasterState)
.When<UserLoggedInWithoutKnownCustomerEvent>()
.Then((state, evnt) =>
{
state.LogUserInToChooseCustomer(evnt.CustomerName);
})
.TransitionTo(WaitingForCustomerNumberState);
InState(MasterState)
.When<UserLoggedInEvent>()
.Then((state, evnt) =>
{
state.UserLogIn(evnt.UserName, evnt.CustomerId);
})
.TransitionTo(OkayState);
InState(MasterState)
.When<NewUserSessionStartedEvent>()
.TransitionTo(UnknownUserState);
Below is how we can change the state of a bug...
// Assign to me ;-)
_machine.Fire(_assignTrigger, "Mihir");
// Mark it resolved
_machine.Fire(Trigger.Resolve);
// Close it
_machine.Fire(Trigger.Close);
So it "works on any phone" - including a Lumia 920 (Windows 8.1) ?
Are there execution times? Like for example, time to create 1000 Bug Objects, and time to change the state of 10000 Bug objects...
(In case I decide to be lazy and put this instead of a proper State Design Pattern.)
For those interested in state machines for game development take a look at the open source lib we made at RSG:
https://github.com/Real-Serious-Games/Fluent-State-Machine
We use it for game states and logic, structuring UI state and simple AI.
In other new, great article! I'm going to use this in my business app I'm developing at the moment - perfect for managing states in a workflow. I'm also thinking it could help with a simple business rules engine, perhaps by using states to signify business rule issues (warnings, errors, etc.).
http://www.codeproject.com/Articles/30305/State-Machine-Complier-And-Asp-net
Especially for longer running (async) tasks, versus the requirement to lock access to the state machine when making transitions.
Comments are closed.