FrenchExDev.Net.FiniteStateMachine.Core 1.0.4

dotnet add package FrenchExDev.Net.FiniteStateMachine.Core --version 1.0.4
                    
NuGet\Install-Package FrenchExDev.Net.FiniteStateMachine.Core -Version 1.0.4
                    
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="FrenchExDev.Net.FiniteStateMachine.Core" Version="1.0.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FrenchExDev.Net.FiniteStateMachine.Core" Version="1.0.4" />
                    
Directory.Packages.props
<PackageReference Include="FrenchExDev.Net.FiniteStateMachine.Core" />
                    
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 FrenchExDev.Net.FiniteStateMachine.Core --version 1.0.4
                    
#r "nuget: FrenchExDev.Net.FiniteStateMachine.Core, 1.0.4"
                    
#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 FrenchExDev.Net.FiniteStateMachine.Core@1.0.4
                    
#: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=FrenchExDev.Net.FiniteStateMachine.Core&version=1.0.4
                    
Install as a Cake Addin
#tool nuget:?package=FrenchExDev.Net.FiniteStateMachine.Core&version=1.0.4
                    
Install as a Cake Tool

FrenchExDev.Net.FiniteStateMachine.Core

A tiny & flexible, enum-typed generic Finite State Machine library for .NET, written in C#.

This library enables you to separate modeling and building of executable stateful machines, with support for custom actions, conditions, and transition-specific behaviors.


Features

  • Strongly-typed FSMs Use your own types for states, triggers, and context objects.
  • Builder Pattern Fluent API for defining states, transitions, and actions.
  • Transition Actions & Conditions Attach logic and guards to transitions.
  • State Entry Actions Execute logic when entering a state.
  • Unit-tested Includes comprehensive tests using a typical FSM scenario.

Getting Started

1. Install

dotnet add package FrenchExDev.Net.FiniteStateMachine.Core

2. Define your Class

Create a class that will represent the context of your state machine:

class Door
{
    public bool Locked { get; set; }
    public void History(DeviceState state, DeviceEvent trigger, DateTime timestamp){ /* ... */ }
    public void ResetAvailableOperations(DeviceOperation[] operations) { /* ... */ }
}

3. Define Your Events and States

Define enums or classes for your states and triggers:

enum DoorState { Closed, Open, Locked }
enum DoorEvent { Open, Close, Lock, Unlock }

4. Build a State Machine

Use the builder to define states and transitions:

using FrenchExDev.Net.FiniteStateMachine.Core;

var builder = new FiniteStateMachineBuilder<Door, DoorState, DoorEvent>();

builder
    .Transition(fromState: DoorState.Closed, toState: DoorState.Open, on: DoorEvent.Open)
    .Transition(fromState: DoorState.Open, toState: DoorState.Closed, on: DoorEvent.Close)
    .Transition(fromState: DoorState.Closed, toState: DoorState.Locked, on: DoorEvent.Lock, body: (door, e, fsm) =>
    {
        door.Locked = true;
    }, condition: (door, e, fsm) => !door.Locked)
    .Transition(fromState: DoorState.Locked, toState: DoorState.Closed, on: DoorEvent.Unlock, body: (door, e, fsm) =>
    {
        door.Locked = false;
    }, condition: (door, e, fsm) => door.Locked)
    .Transition(on: DoorEvent.Open, fromState: DoorState.Locked, toState: DoorState.Open, condition: (door, e, fsm) => door.HasKey)
    ;

var door = new Door();
var fsm = builder.Build(door, DoorState.Closed);

5. Fire triggers

fsm.Fire(DoorEvent.Open);   // Door transitions to Open, returned TransitionResult.Success
fsm.Fire(DoorEvent.Close);  // Door transitions to Closed, returned TransitionResult.Success
fsm.Fire(DoorEvent.Lock);   // Door transitions to Locked, sets door.Locked = true, returned TransitionResult.Success
fsm.Fire(DoorEvent.Unlock); // Door transitions to Closed, sets door.Locked = false, returned TransitionResult.Success

Advanced: State Entry Actions

imagine the following scenarios:

  • You have a device with Available Operations and History
  • Your have its State and Event enums
using FrenchExDev.Net.FiniteStateMachine.Core;

public class Device {
    public Device History(DeviceState state, DeviceEvent trigger, DateTime timestamp) { /* ... */ }
    public Device ResetAvailableOperations(DeviceOperation[] operations) { /* ... */ }
}

public enum DeviceState { NotInitialized, Initing, Available, Connected }
public enum DeviceEvent { Init, Inited, Connect }

You can use the WhenFiniteStateMachineBuilder to define actions that run

  • when a specific event is fired using .On(DeviceEvent trigger, Action<Device, DeviceEvent, WhenFiniteStateMachine<Device, DeviceState, DeviceEvent>> action);
  • whenever a specific state is entered, regardless of the trigger using .When(DeviceState state, Action<Device, DeviceEvent, WhenFiniteStateMachine<Device, DeviceState, DeviceEvent>> action).);
using FrenchExDev.Net.FiniteStateMachine.Core;

var builder = new WhenFiniteStateMachineBuilder<Device, DeviceState, DeviceEvent>();

builder
    .On(DeviceEvent.Open, (device, trigger, fsm) =>     {
        device.History(DeviceState.Open, trigger, DateTime.UtcNow);
    })
    .When(DeviceState.Connected, (device, trigger, fsm) =>
    {
        device.ResetAvailableOperations(new[] { DeviceOperation.Disconnect, DeviceOperation.Upload });
    });

builder
    .Transition(on: DeviceEvent.Init, fromState: DeviceState.NotInitialized, toState: DeviceState.Initing, body: (device, e, fsm) =>
    {
        device.History(DeviceState.Initing, e, DateTime.UtcNow);
        fsm.Fire(DeviceEvent.Inited);
    })
    .Transition(on: DeviceEvent.Inited, fromState: DeviceState.Initing, toState: DeviceState.Available, body: (device, e, fsm) =>
    {
        device.History(DeviceState.Available, e, DateTime.UtcNow);
    })
    .Transition(on: DeviceEvent.Connect, fromState: DeviceState.Available, toState: DeviceState.Connected, body: (device, e, fsm) =>
    {
        device.History(DeviceState.Connected, e, DateTime.UtcNow);
    });

var device = new Device();
var fsm = builder.Build(device, DeviceState.NotInitialized);

fsm.Fire(DeviceEvent.Init);    // Device transitions to Initing, then to Available, returned TransitionResult.Success
fsm.Fire(DeviceEvent.Connect); // Device transitions to Connected, returned TransitionResult.Success

Running Tests

Open a Terminal in the .sln directory and run:

dotnet test

Which should output something like:

PS C:\code\FSM.Net\FSM.Net_i1\FrenchExDev.FiniteStateMachine.Net> dotnet test
Restauration terminée (0,9s)
  FrenchExDev.Net.FiniteStateMachine.Core a réussi (3,7s) → src\FrenchExDev.Net.FiniteStateMachine.Core\bin\Debug\net9.0\FrenchExDev.Net.FiniteStateMachine.Core.dll
  FrenchExDev.Net.FiniteStateMachine.Core.Tests a réussi (0,9s) → test\FrenchExDev.Net.FiniteStateMachine.Core.Tests\bin\Debug\net9.0\FrenchExDev.Net.FiniteStateMachine.Core.Tests.dll
  Test de FrenchExDev.Net.FiniteStateMachine.Core.Tests : a réussi (0,8 s)

Récapitulatif du test : total : 3; échec : 0; réussi : 3; ignoré : 0; durée : 0,4s
Générer a réussi dans 6,7s

Copyright (c) 2025 FrenchExDev Stéphane ERARD email@stephane.erard@gmail.com

Contact for licensing: stephane.erard@gmail.com.

This project is provided for educational purposes only.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  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.
  • net9.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
1.0.4 64 8/15/2025
1.0.3 131 8/14/2025
1.0.2 129 8/14/2025
1.0.1 126 8/14/2025
1.0.0 128 8/14/2025