A Simple State Machine in .NET - Adding Code-based Implementation

Introduction

On a previous post, I introduced my SimpleStateMachine project. Essentially, it provided an easy way to define a state machine from enumeration values. The version presented used attributes on these enumeration values to build up the state machine. Because this can sometimes be intrusive, and we may not want to "pollute" our enumerations, I decided to implement another way to define a state machine from an enumeration.

State Machines from Code

So, I came up with this interface:

public interface IStateMachine<T> where T : Enum
{
    T GetInitialState();
    IStateMachine<T> CanTransitionTo(T state, params IReadOnlyCollection<T> otherStates);
    IEnumerable<T> GetTransitions(T state);
    bool IsFinalState(T state);
    bool IsInitialState(T state);
}

It provides basically the same operations that were previously available:

  • Get the initial state of an enumeration (GetInitialState)
  • Check if an enumeration value is the initial (IsInitialState) or the final (IsFinalState) state
  • Get all possible transitions from a given state (GetTransitions)

But the way to achieve this is quite different: we now need to explicitly create the state machine and feed it with our desired transitions:

var created = TicketState.Created;

var stateMachine = created.Create();
stateMachine.CanTransitionTo(created, TicketState.Ready, TicketState.Closed);
stateMachine.CanTransitionTo(TicketState.Ready, TicketState.Blocked, TicketState.InReview, TicketState.Closed, TicketState.Ready);
stateMachine.CanTransitionTo(TicketState.InProgress, TicketState.InProgress, TicketState.Closed);
stateMachine.CanTransitionTo(TicketState.Blocked, TicketState.InProgress, TicketState.Closed);
stateMachine.CanTransitionTo(TicketState.InReview, TicketState.InProgress, TicketState.Closed);

Notice that the first method to be called, the one that creates the state machine, is Create, and it should be called on the initial state. Then we can add transitions to any state by using the CanTransitionTo method on the returned IStateMachine<T> instance.

When we are done, we can check in a very similar way as we did before:

var initialState = stateMachine.GetInitialState();
var isInitialState = created.IsInitialState();
var isFinalState = created.IsFinalState();
var canTransitionToClosed = created.CanTransitionTo(TicketState.Closed);
var canTransitionToBlocked = created.CanTransitionTo(TicketState.Blocked);
var transitions = created.GetTransitions();
So, essentially, we have a state machine in memory, that we can use and modify at any given time.

Conclusion

Goes without saying, I hope you find this useful, the Nuget package is already updated (targetting .NET 8), and I'll probably be adding some more features in the near future, so stay tuned!

GitHub: https://github.com/rjperes/SimpleStateMachine

Nuget: https://www.nuget.org/packages/SimpleStateMachine

Comments

Popular posts from this blog

OpenTelemetry with ASP.NET Core

ASP.NET Core Middleware

.NET Cancellation Tokens