Philiprehberger.StateMachine 0.3.0

dotnet add package Philiprehberger.StateMachine --version 0.3.0
                    
NuGet\Install-Package Philiprehberger.StateMachine -Version 0.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Philiprehberger.StateMachine" Version="0.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Philiprehberger.StateMachine" Version="0.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Philiprehberger.StateMachine" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Philiprehberger.StateMachine --version 0.3.0
                    
#r "nuget: Philiprehberger.StateMachine, 0.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Philiprehberger.StateMachine@0.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Philiprehberger.StateMachine&version=0.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Philiprehberger.StateMachine&version=0.3.0
                    
Install as a Cake Tool

Philiprehberger.StateMachine

CI NuGet Last updated

Lightweight finite state machine with fluent configuration, guard conditions, and async transition support.

Installation

dotnet add package Philiprehberger.StateMachine

Usage

using Philiprehberger.StateMachine;

enum State { Locked, Unlocked }
enum Trigger { Coin, Push }

var machine = new StateMachineBuilder<State, Trigger>()
    .Configure(State.Locked)
        .Permit(Trigger.Coin, State.Unlocked)
    .Configure(State.Unlocked)
        .Permit(Trigger.Push, State.Locked)
    .Build(State.Locked);

machine.Fire(Trigger.Coin);
// machine.CurrentState == State.Unlocked

Guard Conditions

var machine = new StateMachineBuilder<State, Trigger>()
    .Configure(State.Locked)
        .PermitIf(Trigger.Coin, State.Unlocked, () => HasValidCoin())
    .Build(State.Locked);

bool canFire = machine.CanFire(Trigger.Coin);
var permitted = machine.GetPermittedTriggers();

Entry and Exit Actions

var machine = new StateMachineBuilder<State, Trigger>()
    .Configure(State.Unlocked)
        .OnEntry(() => Console.WriteLine("Gate opened"))
        .OnExit(() => Console.WriteLine("Gate closing"))
        .Permit(Trigger.Push, State.Locked)
    .Build(State.Unlocked);

Async Actions

var machine = new StateMachineBuilder<State, Trigger>()
    .Configure(State.Locked)
        .OnEntryAsync(async () => await NotifyAsync("Locked"))
        .OnExitAsync(async () => await LogAsync("Leaving locked"))
        .Permit(Trigger.Coin, State.Unlocked)
    .Build(State.Locked);

await machine.FireAsync(Trigger.Coin);

Transition History

var machine = new StateMachineBuilder<State, Trigger>()
    .WithMaxHistorySize(50)
    .Configure(State.Locked)
        .Permit(Trigger.Coin, State.Unlocked)
    .Configure(State.Unlocked)
        .Permit(Trigger.Push, State.Locked)
    .Build(State.Locked);

machine.Fire(Trigger.Coin);

foreach (var record in machine.TransitionHistory)
{
    Console.WriteLine($"{record.FromState} -> {record.ToState} via {record.Trigger} at {record.Timestamp}");
}

Hierarchical Substates

enum State { Active, Running, Paused, Inactive }
enum Trigger { Start, Pause, Resume, Stop }

var machine = new StateMachineBuilder<State, Trigger>()
    .Configure(State.Active)
        .Permit(Trigger.Stop, State.Inactive)
    .Configure(State.Running)
        .SubstateOf(State.Active)
        .Permit(Trigger.Pause, State.Paused)
    .Configure(State.Paused)
        .SubstateOf(State.Active)
        .Permit(Trigger.Resume, State.Running)
    .Build(State.Running);

machine.IsInState(State.Active);  // true (Running is a substate of Active)
machine.CanFire(Trigger.Stop);    // true (inherited from Active)

Serialization and Restoration

// Capture a snapshot
var snapshot = machine.Serialize();
string json = JsonSerializer.Serialize(snapshot);

// Restore from snapshot
var deserialized = JsonSerializer.Deserialize<StateMachineSnapshot<State, Trigger>>(json);
var restored = StateMachine<State, Trigger>.Restore(deserialized, builder);

API

StateMachineBuilder<TState, TTrigger>

Method Description
Configure(TState state) Begin configuring a state, returns StateConfiguration
WithMaxHistorySize(int maxSize) Set the maximum number of transition history entries (default 100)
Build(TState initialState) Create the state machine with the specified initial state

StateConfiguration<TState, TTrigger>

Method Description
Permit(TTrigger, TState) Allow a transition on the given trigger
PermitIf(TTrigger, TState, Func<bool>) Allow a transition with a guard condition
OnEntry(Action) Execute action when entering this state
OnExit(Action) Execute action when exiting this state
OnEntryAsync(Func<Task>) Execute async action when entering this state
OnExitAsync(Func<Task>) Execute async action when exiting this state
SubstateOf(TState parentState) Declare this state as a substate of the given parent

StateMachine<TState, TTrigger>

Member Description
CurrentState The current state of the machine
TransitionHistory Read-only list of recorded transitions
MaxHistorySize Maximum number of history entries retained
Fire(TTrigger) Fire a trigger synchronously
FireAsync(TTrigger) Fire a trigger asynchronously
CanFire(TTrigger) Check if a trigger can be fired from the current state
GetPermittedTriggers() Get all triggers permitted from the current state
IsInState(TState) Check if the machine is in the given state or a substate thereof
Serialize() Create a JSON-serializable snapshot of the machine
Restore(snapshot, builder) Static method to rebuild a machine from a snapshot

TransitionRecord<TState, TTrigger>

Property Type Description
FromState TState The state before the transition
ToState TState The state after the transition
Trigger TTrigger The trigger that caused the transition
Timestamp DateTimeOffset When the transition occurred

InvalidTransitionException

Thrown when attempting to fire a trigger that is not permitted from the current state.

Development

dotnet build src/Philiprehberger.StateMachine.csproj --configuration Release

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0 121 4/1/2026
0.2.0 117 3/28/2026
0.1.0 109 3/23/2026