StateCore 0.2.0
dotnet add package StateCore --version 0.2.0
NuGet\Install-Package StateCore -Version 0.2.0
<PackageReference Include="StateCore" Version="0.2.0" />
<PackageVersion Include="StateCore" Version="0.2.0" />
<PackageReference Include="StateCore" />
paket add StateCore --version 0.2.0
#r "nuget: StateCore, 0.2.0"
#:package StateCore@0.2.0
#addin nuget:?package=StateCore&version=0.2.0
#tool nuget:?package=StateCore&version=0.2.0
StateCore
A flexible and type-safe finite state machine library for C#.
Features
- 🎯 Type-Safe: Strongly typed states and triggers using enums or custom classes
- 🔄 Flexible Transitions: Define complex state transitions with granular control
- 🎬 Per-Transition Lifecycle Hooks: Execute specific actions for individual transitions
- 🔗 Fluent API: Intuitive builder pattern for readable state machine definitions
- ⚡ Multiple Actions: Chain multiple entry/exit actions per transition
- 🧩 Extensible: Works with enums or custom IEquatable types
Installation
dotnet add package StateCore
Or via Package Manager:
Install-Package StateCore
Quick Start
using StateCore;
// Define your states and triggers
public enum State { Paused, Playing, Stopped }
public enum Trigger { Play, Pause, Stop }
// Create the state machine
var stateMachine = StateMachine<State, Trigger>
.WithInitialState(State.Paused)
.State(State.Paused, cfg =>
{
// Transition: Paused → Playing
cfg
.OnExit(() => Console.WriteLine("Leaving Paused state"))
.OnEnter(() => Console.WriteLine("Entering Playing state"))
.On(Trigger.Play)
.GoTo(State.Playing);
// Transition: Paused → Stopped
cfg
.OnExit(() => Console.WriteLine("Leaving Paused state"))
.OnEnter(() => Console.WriteLine("Entering Stopped state"))
.On(Trigger.Stop)
.GoTo(State.Stopped);
})
.State(State.Playing, cfg => cfg
.OnExit(() => Console.WriteLine("Leaving Playing state"))
.OnEnter(() => Console.WriteLine("Entering Paused state"))
.On(Trigger.Pause)
.GoTo(State.Paused))
.State(State.Stopped, cfg => cfg
.OnEnter(() => Console.WriteLine("Entering Playing state from Stopped"))
.On(Trigger.Play)
.GoTo(State.Playing))
.Build();
// Trigger state transitions
stateMachine.Trigger(Trigger.Play);
// Output:
// Leaving Paused state
// Entering Playing state
Console.WriteLine($"Current state: {stateMachine.CurrentState}"); // Playing
Understanding Transitions
Important: In StateCore, the lifecycle hooks work as follows:
OnExit()- Executes when leaving the current state (the state being configured)OnEnter()- Executes when entering the target state (the state inGoTo())
.State(State.Paused, cfg =>
{
cfg
.OnExit(() => Console.WriteLine("Exiting Paused")) // Runs when leaving Paused
.OnEnter(() => Console.WriteLine("Entering Playing")) // Runs when entering Playing
.On(Trigger.Play)
.GoTo(State.Playing); // ← OnEnter refers to THIS state
})
Each configuration chain represents a specific transition path with its own unique behavior.
Advanced Usage
Multiple Actions Per Transition
Chain multiple actions that execute in order when entering the target state:
.State(State.Stopped, cfg => cfg
.OnExit(() => Console.WriteLine("Leaving Stopped state"))
.OnEnter(() => Console.WriteLine("1. Initializing audio system"))
.OnEnter(() => Console.WriteLine("2. Loading media file"))
.OnEnter(() => Console.WriteLine("3. Starting playback"))
.OnEnter(() => Console.WriteLine("4. Updating UI to Playing"))
.On(Trigger.Play)
.GoTo(State.Playing))
When Trigger.Play is fired:
Output:
Leaving Stopped state
1. Initializing audio system
2. Loading media file
3. Starting playback
4. Updating UI to Playing
Different Entry Behavior for Same Target State
You can enter the same state from different sources with different behavior:
// From Paused
.State(State.Paused, cfg => cfg
.OnExit(() => Console.WriteLine("Resuming from pause"))
.OnEnter(() => Console.WriteLine("Continuing playback"))
.On(Trigger.Play)
.GoTo(State.Playing))
// From Stopped
.State(State.Stopped, cfg => cfg
.OnExit(() => Console.WriteLine("Starting fresh"))
.OnEnter(() => Console.WriteLine("Beginning new playback"))
.On(Trigger.Play)
.GoTo(State.Playing))
Both transitions go to State.Playing, but with different OnEnter actions!
Multiple Transitions from Same State
Define different behavior for each outgoing transition:
.State(State.Playing, cfg =>
{
// Transition: Playing → Paused
cfg
.OnExit(() => Console.WriteLine("Pausing playback"))
.OnEnter(() => Console.WriteLine("Now paused"))
.OnEnter(() => SavePlaybackPosition())
.On(Trigger.Pause)
.GoTo(State.Paused);
// Transition: Playing → Stopped
cfg
.OnExit(() => Console.WriteLine("Stopping playback"))
.OnExit(() => ReleaseResources())
.OnEnter(() => Console.WriteLine("Fully stopped"))
.OnEnter(() => ResetPlaybackPosition())
.On(Trigger.Stop)
.GoTo(State.Stopped);
})
Using Custom Classes as States
Instead of enums, use custom classes that implement IEquatable<T>:
public class State : IEquatable<State>
{
public int Id { get; }
public string Name { get; }
public State(int id, string name)
{
Id = id;
Name = name;
}
public bool Equals(State? other)
{
if (other is null) return false;
return Id == other.Id && Name == other.Name;
}
public override bool Equals(object? obj) => Equals(obj as State);
public override int GetHashCode() => HashCode.Combine(Id, Name);
public static bool operator ==(State? left, State? right) => Equals(left, right);
public static bool operator !=(State? left, State? right) => !Equals(left, right);
}
// Define state instances
var paused = new State(1, "Paused");
var playing = new State(2, "Playing");
// Use in state machine
var stateMachine = StateMachine<State, Trigger>
.WithInitialState(paused)
.State(paused, cfg => cfg
.OnExit(() => Console.WriteLine($"Leaving {paused.Name}"))
.OnEnter(() => Console.WriteLine($"Entering {playing.Name}"))
.On(Trigger.Play)
.GoTo(playing))
.Build();
Complete Example: Media Player
public enum State { Stopped, Playing, Paused, Buffering }
public enum Trigger { Play, Pause, Stop, Buffer, Resume }
var mediaPlayer = StateMachine<State, Trigger>
.WithInitialState(State.Stopped)
.State(State.Stopped, cfg =>
{
// Stopped → Playing
cfg
.OnExit(() => Console.WriteLine("Starting player"))
.OnEnter(() => Console.WriteLine("Loading media"))
.OnEnter(() => Console.WriteLine("Playback started"))
.On(Trigger.Play)
.GoTo(State.Playing);
})
.State(State.Playing, cfg =>
{
// Playing → Paused
cfg
.OnExit(() => Console.WriteLine("Pausing playback"))
.OnEnter(() => Console.WriteLine("Playback paused"))
.OnEnter(() => SavePosition())
.On(Trigger.Pause)
.GoTo(State.Paused);
// Playing → Stopped
cfg
.OnExit(() => Console.WriteLine("Stopping playback"))
.OnExit(() => ReleaseResources())
.OnEnter(() => Console.WriteLine("Player stopped"))
.On(Trigger.Stop)
.GoTo(State.Stopped);
// Playing → Buffering
cfg
.OnExit(() => Console.WriteLine("Connection slow"))
.OnEnter(() => Console.WriteLine("Buffering content..."))
.OnEnter(() => ShowSpinner())
.On(Trigger.Buffer)
.GoTo(State.Buffering);
})
.State(State.Paused, cfg =>
{
// Paused → Playing
cfg
.OnExit(() => Console.WriteLine("Resuming playback"))
.OnEnter(() => Console.WriteLine("Playback resumed"))
.On(Trigger.Play)
.GoTo(State.Playing);
// Paused → Stopped
cfg
.OnExit(() => Console.WriteLine("Stopping from pause"))
.OnEnter(() => Console.WriteLine("Player stopped"))
.On(Trigger.Stop)
.GoTo(State.Stopped);
})
.State(State.Buffering, cfg =>
{
// Buffering → Playing
cfg
.OnExit(() => Console.WriteLine("Buffer filled"))
.OnExit(() => HideSpinner())
.OnEnter(() => Console.WriteLine("Resuming playback"))
.On(Trigger.Resume)
.GoTo(State.Playing);
})
.Build();
// Use the state machine
mediaPlayer.Trigger(Trigger.Play);
// Output:
// Starting player
// Loading media
// Playback started
mediaPlayer.Trigger(Trigger.Pause);
// Output:
// Pausing playback
// Playback paused
// (SavePosition called)
API Reference
StateMachine<TState, TTrigger>
Builder Methods
WithInitialState(TState state)- Sets the starting stateState(TState state, Action<StateConfiguration> configure)- Configures transitions originating from a stateBuild()- Creates the state machine instance
Instance Methods
Trigger(TTrigger trigger)- Fires a trigger to execute a state transitionCurrentState- Gets the current state (property)
StateConfiguration (Transition Chain)
Each configuration chain represents one specific transition from the current state to a target state.
Methods
OnExit(Action action)- Executes when leaving the current state (the state being configured)OnEnter(Action action)- Executes when entering the target state (the state specified inGoTo())On(TTrigger trigger)- Defines which trigger activates this transitionGoTo(TState nextState)- Specifies the destination state for this transition
Execution Order
When a trigger is fired:
- All
OnExitactions for the transition (leaving current state) - All
OnEnteractions for the transition (entering target state) - State changes to the target state
Common Use Cases
- Game State Management: Menu → Playing → Paused → GameOver
- Workflow Engines: Draft → Review → Approved → Published
- Connection Management: Disconnected → Connecting → Connected → Error
- Media Players: Stopped → Playing → Paused → Buffering
- Document Lifecycle: New → InProgress → Review → Completed
- Order Processing: Pending → Processing → Shipped → Delivered
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Links
| Product | Versions 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. |
-
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.