FrenchExDev.Net.FiniteStateMachine.Core 1.0.3

There is a newer version of this package available.
See the version list below for details.
dotnet add package FrenchExDev.Net.FiniteStateMachine.Core --version 1.0.3
                    
NuGet\Install-Package FrenchExDev.Net.FiniteStateMachine.Core -Version 1.0.3
                    
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.3" />
                    
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.3" />
                    
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.3
                    
#r "nuget: FrenchExDev.Net.FiniteStateMachine.Core, 1.0.3"
                    
#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.3
                    
#: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.3
                    
Install as a Cake Addin
#tool nuget:?package=FrenchExDev.Net.FiniteStateMachine.Core&version=1.0.3
                    
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 workflows, 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. Create a 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, body: (door, e, fsm) => {
        door.Locked = true;
    })
    .Transition(fromState: DoorState.Closed, toState: DoorState.Locked, on: DoorEvent.Lock)
    .Transition(fromState: DoorState.Locked, toState: DoorState.Closed, on: DoorEvent.Unlock, body: (door, e, fsm) =>
    {
        door.Locked = false;
    });

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:

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 trigger is fired using .On(DeviceEvent trigger, Action<Device, DeviceEvent, WhenFiniteStateMachine<Device, DeviceState, DeviceEvent>> action);
  • whenever a 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

Navigate to the test/FrenchExDev.Net.FiniteStateMachine.Core.Tests directory and run:

dotnet test

Copyright (c) 2025 FrenchExDev Stéphane ERARD

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 67 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 130 8/14/2025