DEV - State Machines in C#

 

🚀 Introduction

Have you ever found yourself tangled in if/else statements just to manage the different statuses of an order, a document, or a ticket?
If so, you’re not alone — and there’s a better way: the state machine.

In this post, we’ll explore:

  • What a state machine is

  • Why it’s useful in real-world applications

  • And build a simple C# example modeling a common business process: order processing


🧩 What is a state machine?

At its core, a state machine is a design pattern that models:

  • A finite set of states an entity can be in

  • A set of allowed transitions between those states, usually triggered by events or commands

This makes your code:
✅ Easier to read
✅ Easier to test
✅ More robust (can’t accidentally transition to an invalid state)


📦 Common real-world example: Order Processing

An order typically goes through states like:

  1. New

  2. Paid

  3. Shipped

  4. Delivered

  5. (Optionally) Returned or Cancelled

And these transitions happen based on business events like: customer payment, warehouse shipping, delivery confirmation, or cancellation.






⚙️ Implementing it in C# with Stateless

Let’s see how to implement this example step by step.

Step 1: Install Stateless

dotnet add package Stateless

Step 2: Define states and triggers


public enum OrderState { New, Paid, Shipped, Delivered, Cancelled, Returned } public enum OrderTrigger { Pay, Ship, Deliver, Cancel, Return }

Step 3: Configure the state machine

using Stateless;
public class Order { public OrderState State { get; private set; } private StateMachine<OrderState, OrderTrigger> _machine; public Order() { State = OrderState.New; _machine = new StateMachine<OrderState, OrderTrigger>(() => State, s => State = s); _machine.Configure(OrderState.New) .Permit(OrderTrigger.Pay, OrderState.Paid) .Permit(OrderTrigger.Cancel, OrderState.Cancelled); _machine.Configure(OrderState.Paid) .Permit(OrderTrigger.Ship, OrderState.Shipped) .Permit(OrderTrigger.Cancel, OrderState.Cancelled); _machine.Configure(OrderState.Shipped) .Permit(OrderTrigger.Deliver, OrderState.Delivered) .Permit(OrderTrigger.Return, OrderState.Returned); // Delivered can only be returned _machine.Configure(OrderState.Delivered) .Permit(OrderTrigger.Return, OrderState.Returned); } public void Pay() => _machine.Fire(OrderTrigger.Pay); public void Ship() => _machine.Fire(OrderTrigger.Ship); public void Deliver() => _machine.Fire(OrderTrigger.Deliver); public void Cancel() => _machine.Fire(OrderTrigger.Cancel); public void Return() => _machine.Fire(OrderTrigger.Return); }

Step 4: Use it

var order = new Order();
Console.WriteLine($"Current state: {order.State}"); // New order.Pay(); Console.WriteLine($"After payment: {order.State}"); // Paid order.Ship(); Console.WriteLine($"After shipping: {order.State}"); // Shipped order.Deliver(); Console.WriteLine($"After delivery: {order.State}"); // Delivered order.Return(); Console.WriteLine($"After return: {order.State}"); // Returned

Why this is powerful

Instead of scattering business rules everywhere, you:

  • Centralize all allowed transitions

  • Prevent invalid flows (e.g., can’t ship an unpaid order)

  • Make it easy to extend (add states like “Refunded” later)

It also makes your logic easier to unit test and visualize.


When to use a state machine

Use it when:

  • Your entity has clearly defined states

  • Business rules depend on current state

  • You need to prevent invalid transitions

Common examples:

  • Order lifecycle

  • Document approval flows

  • User account status

  • Ticketing systems

  • Manufacturing processes


📌 Conclusion

State machines are a practical way to manage complexity and keep your code clean.
In C#, the Stateless library makes implementing them straightforward, even in large systems.


📚 Further reading

No comments:

Theme images by merrymoonmary. Powered by Blogger.