drittich.StateMachine
1.2.1
dotnet add package drittich.StateMachine --version 1.2.1
NuGet\Install-Package drittich.StateMachine -Version 1.2.1
<PackageReference Include="drittich.StateMachine" Version="1.2.1" />
paket add drittich.StateMachine --version 1.2.1
#r "nuget: drittich.StateMachine, 1.2.1"
// Install drittich.StateMachine as a Cake Addin #addin nuget:?package=drittich.StateMachine&version=1.2.1 // Install drittich.StateMachine as a Cake Tool #tool nuget:?package=drittich.StateMachine&version=1.2.1
State Machine
A simple, extensible finite state machine that allows you to define states, events, transitions, and pass event data through to your transition actions.
Installation
The drittich.StateMachine
library is available on NuGet. You can install it using the Package Manager Console:
Install-Package drittich.StateMachine
Or using the .NET CLI:
dotnet add package drittich.StateMachine
This will install the library and its dependencies.
Example Usage
The StateMachine
class lets you define your own states, events, transitions, and a data transfer object (DTO) to pass data with events. This data can then be provided to the action that runs when a transition occurs.
You need to:
- Define enums for your states and events.
- Create a DTO class for event data.
- Initialize the state machine with an initial state and a logger.
- Add transitions that specify how the state machine moves from one state to another in response to events.
Define States and Events
enum MyStates
{
Initial,
SomeState,
SomeOtherState,
Complete
}
enum MyEvents
{
SomethingHappened,
SomethingElseHappened,
SomeOtherRandomEvent
}
Create a DTO for Event Data
public class MyDto
{
public int Prop1 { get; set; }
}
Initialize the State Machine
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
// Create a logger (use NullLogger if you don't need logging)
ILogger<StateMachine<MyStates, MyEvents, MyDto>> logger = new NullLogger<StateMachine<MyStates, MyEvents, MyDto>>();
// Initialize the state machine with the initial state
var sm = new StateMachine<MyStates, MyEvents, MyDto>(MyStates.Initial, logger);
Define the Transitions
With the simplified AddTransition method, you can now add transitions directly without needing to create Transition objects explicitly:
// Add transitions to the state machine
sm.AddTransition(MyStates.Initial, MyEvents.SomethingHappened, MyStates.SomeState, SomeMethodToExecuteAsync);
sm.AddTransition(MyStates.SomeState, MyEvents.SomethingElseHappened, MyStates.Complete, SomeOtherMethodToExecuteAsync);
Define the Action Methods
using System.Threading;
using System.Threading.Tasks;
// Action method for the first transition
async Task SomeMethodToExecuteAsync(MyDto data, CancellationToken cancellationToken)
{
// Your action code here
await Task.Delay(100, cancellationToken);
Console.WriteLine("Executed SomeMethodToExecuteAsync");
}
// Action method for the second transition
async Task SomeOtherMethodToExecuteAsync(MyDto data, CancellationToken cancellationToken)
{
// Your action code here
await Task.Delay(100, cancellationToken);
Console.WriteLine("Executed SomeOtherMethodToExecuteAsync");
}
Execute Transitions
var data = new MyDto { Prop1 = 1 };
// Execute the first transition
var resultingState = await sm.GetNextAsync(MyEvents.SomethingHappened, data);
// resultingState is MyStates.SomeState
// Execute the second transition
var resultingState2 = await sm.GetNextAsync(MyEvents.SomethingElseHappened, data);
// resultingState2 is MyStates.Complete
Handle Invalid Transitions
If an invalid transition is attempted (no transition is defined for the current state and event), an InvalidTransitionException is thrown.
try
{
// Attempt an invalid transition
var resultingState3 = await sm.GetNextAsync(MyEvents.SomeOtherRandomEvent, data);
}
catch (InvalidTransitionException ex)
{
// Handle the exception
Console.WriteLine($"Invalid transition: {ex.Message}");
}
Additional Features
Guard Conditions
You can add guard conditions to transitions to control whether the transition should occur based on the event data.
sm.AddTransition(
MyStates.SomeState,
MyEvents.SomeOtherRandomEvent,
MyStates.Complete,
SomeOtherMethodToExecuteAsync,
guard: data => data.Prop1 > 0
);
If the guard condition returns false, a GuardConditionFailedException is thrown, and the transition does not occur.
Cancellation Support
The action methods accept a CancellationToken, allowing transitions to be canceled if needed.
var cts = new CancellationTokenSource();
cts.CancelAfter(500); // Cancel after 500ms
try
{
await sm.GetNextAsync(MyEvents.SomethingHappened, data, cts.Token);
}
catch (TaskCanceledException)
{
Console.WriteLine("Transition was canceled.");
}
Logging
The state machine uses ILogger to log information about transitions, warnings, and errors.
- Information: Successful transitions.
- Warning: Undefined transitions or guard condition failures.
- Error: Exceptions thrown during actions.
Exception Handling
- InvalidTransitionException: Thrown when no transition is defined for the current state and event.
- GuardConditionFailedException: Thrown when a guard condition evaluates to false.
- TaskCanceledException: Thrown when a transition is canceled via a CancellationToken.
- InvalidOperationException: Thrown when attempting to add a duplicate transition.
Thread Safety
The StateMachine
class is thread-safe and can handle concurrent transition attempts appropriately. It ensures that only one transition occurs at a time, maintaining the integrity of the CurrentState
.
Complete Example
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
// Define states and events
enum MyStates
{
Initial,
SomeState,
SomeOtherState,
Complete
}
enum MyEvents
{
SomethingHappened,
SomethingElseHappened,
SomeOtherRandomEvent
}
// DTO for event data
public class MyDto
{
public int Prop1 { get; set; }
}
class Program
{
static async Task Main(string[] args)
{
// Create a logger
ILogger<StateMachine<MyStates, MyEvents, MyDto>> logger = new NullLogger<StateMachine<MyStates, MyEvents, MyDto>>();
// Initialize the state machine
var sm = new StateMachine<MyStates, MyEvents, MyDto>(MyStates.Initial, logger);
// Define transitions
sm.AddTransition(MyStates.Initial, MyEvents.SomethingHappened, MyStates.SomeState, SomeMethodToExecuteAsync);
sm.AddTransition(MyStates.SomeState, MyEvents.SomethingElseHappened, MyStates.Complete, SomeOtherMethodToExecuteAsync);
// Event data
var data = new MyDto { Prop1 = 1 };
// Execute transitions
var resultingState = await sm.GetNextAsync(MyEvents.SomethingHappened, data);
Console.WriteLine($"State after first transition: {resultingState}");
var resultingState2 = await sm.GetNextAsync(MyEvents.SomethingElseHappened, data);
Console.WriteLine($"State after second transition: {resultingState2}");
// Handle invalid transition
try
{
await sm.GetNextAsync(MyEvents.SomeOtherRandomEvent, data);
}
catch (InvalidTransitionException ex)
{
Console.WriteLine($"Invalid transition: {ex.Message}");
}
}
// Action methods
static async Task SomeMethodToExecuteAsync(MyDto data, CancellationToken cancellationToken)
{
await Task.Delay(100, cancellationToken);
Console.WriteLine("Executed SomeMethodToExecuteAsync");
}
static async Task SomeOtherMethodToExecuteAsync(MyDto data, CancellationToken cancellationToken)
{
await Task.Delay(100, cancellationToken);
Console.WriteLine("Executed SomeOtherMethodToExecuteAsync");
}
}
Installation
To use the StateMachine
class in your project, include the source code or compile it into a library that you can reference.
Dependencies
- .NET Standard 2.0 or higher.
Microsoft.Extensions.Logging.Abstractions
for logging interfaces.
Install via NuGet:
Install-Package Microsoft.Extensions.Logging.Abstractions
License
This project is licensed under the MIT License.
Contributing
Contributions are welcome! Please submit a pull request or open an issue to discuss improvements or features.
Contact
For questions or support, please open an issue on the GitHub repository.
Note: Replace SomeMethodToExecuteAsync
and SomeOtherMethodToExecuteAsync
with your actual action methods. The DTO MyDto should contain the data relevant to your application.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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. |
.NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.1 is compatible. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Execute the action outside the transition lock