State machines might sound like a complex topic, but at their core, they’re just a way to manage different states in a system and control how things transition from one state to another.
In this post, we’ll break down what state machines are, why they’re useful, and how to implement one in C# using a straightforward approach. We’ll also walk through a real-world example to see them in action.
What is a State Machine?
A state machine is a pattern used to control the flow of a system by defining possible states and the rules that determine how transitions between those states happen. It consists of:
States : Different conditions or modes the system can be in.
Transitions : Rules that define how the system moves from one state to another.
Events : Triggers that cause state transitions.
Actions : Operations performed when entering, exiting, or staying in a state.
Why Use a State Machine?
State machines provide several benefits when managing workflows:
Improved Readability : Helps organize complex logic in an understandable way.
Encapsulation of State Logic : Keeps state-specific behavior separate from other business logic.
Predictability and Maintainability : Makes it easier to track and debug state changes.
Flexibility : New states and transitions can be added without major changes.
Implementing a State Machine in C#
Let’s implement a simple state machine using a dictionary to manage state transitions.
Use Case: Order Processing System
Imagine you’re building an e-commerce application that handles order statuses. An order can go through several states before completion.
States and Transitions
States :
PendingPayment
,Processing
,Shipped
,Delivered
,Cancelled
-
Events :
-
Pay
(moves from PendingPayment to Processing) -
Ship
(moves from Processing to Shipped) -
Deliver
(moves from Shipped to Delivered) -
Cancel
(moves from PendingPayment to Cancelled or from Processing to Cancelled)
-
C# Implementation
- State Machine Logic :
using System;
using System.Collections.Generic;
class OrderStateMachine
{
public enum OrderState { PendingPayment, Processing, Shipped, Delivered, Cancelled }
public enum OrderTrigger { Pay, Ship, Deliver, Cancel }
private Dictionary<(OrderState, OrderTrigger), OrderState> _stateTransitions;
public OrderState CurrentState { get; private set; }
public OrderStateMachine()
{
CurrentState = OrderState.PendingPayment;
_stateTransitions = new Dictionary<(OrderState, OrderTrigger), OrderState>
{
{ (OrderState.PendingPayment, OrderTrigger.Pay), OrderState.Processing },
{ (OrderState.PendingPayment, OrderTrigger.Cancel), OrderState.Cancelled },
{ (OrderState.Processing, OrderTrigger.Ship), OrderState.Shipped },
{ (OrderState.Processing, OrderTrigger.Cancel), OrderState.Cancelled },
{ (OrderState.Shipped, OrderTrigger.Deliver), OrderState.Delivered }
};
}
public void ProcessTransition(OrderTrigger trigger)
{
if (_stateTransitions.TryGetValue((CurrentState, trigger), out OrderState newState))
{
Console.WriteLine($"Moving from {CurrentState} to {newState} due to {trigger}");
CurrentState = newState;
}
else
{
Console.WriteLine($"Invalid transition: {CurrentState} cannot handle {trigger}");
}
}
}
- Client :
class Program
{
static void Main()
{
var orderStateMachine = new OrderStateMachine();
Console.WriteLine($"Initial state: {orderStateMachine.CurrentState}");
// Simulating order processing
orderStateMachine.ProcessTransition(OrderStateMachine.OrderTrigger.Pay);
orderStateMachine.ProcessTransition(OrderStateMachine.OrderTrigger.Ship);
orderStateMachine.ProcessTransition(OrderStateMachine.OrderTrigger.Deliver);
}
}
How It Works
The
OrderStateMachine
class encapsulates state logic, making the main program cleaner.The
CurrentState
property tracks the current order state.The
_stateTransitions
dictionary holds valid state transitions.The
ProcessTransition()
method checks for valid transitions and updates the state.The
Main
method initializes the state machine and processes transitions.
Conclusion
State machines are an excellent way to manage complex workflows in a clean, predictable manner. Using a simple dictionary in C#, we can build a flexible and maintainable system to handle order states. By encapsulating state logic in a class, we make our code cleaner, more modular, and easier to maintain. Whether you're working on UI workflows, business processes, or game logic, state machines provide a structured approach to managing state transitions effectively.