一、状态机的核心概念
1. 什么是状态机?
状态机(Finite State Machine, FSM,又称有限状态机) 是一种数学模型和编程思想,用于描述一个对象(或系统)在其生命周期内的有限个状态 ,以及这些状态之间的转移规则 、触发转移的事件 / 条件 ,还有状态转移过程中(或处于某个状态时)需要执行的动作 / 行为。
简单理解:它就像对象的 "行为剧本",规定了对象在什么状态下,遇到什么事情,应该切换到什么新状态,同时要做什么事。
2. 状态机的核心组成要素
一个完整的有限状态机包含 4 个核心部分,缺一不可:
- 状态(State):对象的离散状态,数量有限。例如 "电梯停止""电梯上行""电梯下行";"订单待支付""订单已支付""订单已取消"。
- 事件(Event/Trigger):触发状态转移的外部或内部条件 / 信号。例如 "乘客按下 5 楼按钮"(触发电梯上行)、"用户完成支付"(触发订单从待支付→已支付)。
- 转移(Transition):从一个状态切换到另一个状态的规则,通常是 "当前状态 + 事件→目标状态"。例如 "电梯停止(当前状态)+ 按下 5 楼按钮(事件)→ 电梯上行(目标状态)"。
- 动作(Action):在状态转移过程中(或进入 / 退出某个状态时)执行的逻辑。例如 "电梯切换到上行状态时,启动电机并播报'前往 5 楼'"、"订单变为已支付时,生成物流单号并发送通知"。
3. 状态机的核心价值(解决什么问题)
在编程中,状态机主要解决复杂状态切换的逻辑混乱问题,具体价值体现在:
- 消除大量嵌套
if/else或switch/case,让状态相关逻辑更清晰、更易维护; - 明确状态转移规则,减少 "非法状态" 出现的概率(例如订单已取消后,无法再触发支付);
- 状态与行为解耦,便于扩展新状态和新转移规则(符合开闭原则);
- 逻辑可追溯性强,每个状态的行为和转移都有明确定义,便于调试。
二、C# 中状态机的两种实现方式
下面以电商订单状态流转为场景(最经典的状态机应用场景之一),讲解两种常用实现方式。
场景定义(统一约束)
先明确订单的核心状态、事件和转移规则:
|--------------|-------------|----------------|-----------------|
| 核心状态(State) | 触发事件(Event) | 目标状态 | 执行动作(Action) |
| 待支付(Pending) | 用户支付成功 | 已支付(Paid) | 生成物流单号、发送支付成功通知 |
| 待支付(Pending) | 用户取消订单 | 已取消(Cancelled) | 释放库存、发送取消通知 |
| 已支付(Paid) | 商家发货 | 已发货(Shipped) | 更新物流信息、发送发货通知 |
| 已发货(Shipped) | 用户确认收货 | 已完成(Completed) | 完成订单结算、关闭交易 |
前置准备:定义枚举(状态和事件)
首先用 C# 枚举明确订单的状态和触发事件,提高代码可读性和类型安全性:
cs
using System;
using System.Collections.Generic;
// 订单状态枚举
public enum OrderState
{
Pending, // 待支付
Paid, // 已支付
Shipped, // 已发货
Completed, // 已完成
Cancelled // 已取消
}
// 订单事件枚举(触发状态转移的信号)
public enum OrderEvent
{
PaySuccess, // 支付成功
CancelOrder, // 取消订单
ShipGoods, // 商家发货
ConfirmReceipt // 用户确认收货
}
方式 1:简单状态机(基于Dictionary+ 委托,适合简单场景)
这种方式无需复杂的类结构,通过字典存储 "当前状态 + 事件→转移规则",配合委托封装动作,实现轻量级状态机。
cs
/// <summary>
/// 简单订单状态机(基于Dictionary+委托)
/// </summary>
public class SimpleOrderStateMachine
{
// 当前订单状态
public OrderState CurrentState { get; private set; }
// 订单编号(附加信息)
public string OrderId { get; }
// 定义转移规则的委托:参数(事件),返回值(是否转移成功)
private delegate bool StateTransitionDelegate(OrderEvent @event);
// 存储所有状态的转移逻辑:Key=当前状态,Value=该状态对应的转移委托
private readonly Dictionary<OrderState, StateTransitionDelegate> _stateTransitions;
/// <summary>
/// 构造函数:初始化订单状态机
/// </summary>
/// <param name="orderId">订单编号</param>
public SimpleOrderStateMachine(string orderId)
{
OrderId = orderId;
CurrentState = OrderState.Pending; // 初始状态为待支付
// 初始化状态转移规则
_stateTransitions = new Dictionary<OrderState, StateTransitionDelegate>()
{
{ OrderState.Pending, HandlePendingState },
{ OrderState.Paid, HandlePaidState },
{ OrderState.Shipped, HandleShippedState },
{ OrderState.Completed, HandleCompletedState },
{ OrderState.Cancelled, HandleCancelledState }
};
}
/// <summary>
/// 触发事件,驱动状态机转移
/// </summary>
/// <param name="event">触发事件</param>
/// <returns>转移是否成功</returns>
public bool TriggerEvent(OrderEvent @event)
{
if (_stateTransitions.TryGetValue(CurrentState, out var transitionDelegate))
{
return transitionDelegate(@event);
}
Console.WriteLine($"【订单{OrderId}】未知状态{CurrentState},无法处理事件{@event}");
return false;
}
#region 各状态的转移逻辑处理(核心:状态+事件→转移+动作)
/// <summary>
/// 处理"待支付"状态的转移逻辑
/// </summary>
private bool HandlePendingState(OrderEvent @event)
{
switch (@event)
{
case OrderEvent.PaySuccess:
// 执行状态转移前/后的动作
Console.WriteLine($"【订单{OrderId}】开始处理支付成功逻辑...");
GenerateLogisticsNo(); // 生成物流单号
SendNotification("支付成功,订单已确认"); // 发送通知
// 更新当前状态
CurrentState = OrderState.Paid;
Console.WriteLine($"【订单{OrderId}】状态已从{OrderState.Pending}→{OrderState.Paid}");
return true;
case OrderEvent.CancelOrder:
Console.WriteLine($"【订单{OrderId}】开始处理取消订单逻辑...");
ReleaseStock(); // 释放库存
SendNotification("订单已取消,库存已释放");
CurrentState = OrderState.Cancelled;
Console.WriteLine($"【订单{OrderId}】状态已从{OrderState.Pending}→{OrderState.Cancelled}");
return true;
default:
Console.WriteLine($"【订单{OrderId}】待支付状态无法处理事件{@event}");
return false;
}
}
/// <summary>
/// 处理"已支付"状态的转移逻辑
/// </summary>
private bool HandlePaidState(OrderEvent @event)
{
if (@event == OrderEvent.ShipGoods)
{
Console.WriteLine($"【订单{OrderId}】开始处理商家发货逻辑...");
UpdateLogisticsInfo("顺丰速运:SF1234567890");
SendNotification("商家已发货,请注意查收");
CurrentState = OrderState.Shipped;
Console.WriteLine($"【订单{OrderId}】状态已从{OrderState.Paid}→{OrderState.Shipped}");
return true;
}
Console.WriteLine($"【订单{OrderId}】已支付状态无法处理事件{@event}");
return false;
}
/// <summary>
/// 处理"已发货"状态的转移逻辑
/// </summary>
private bool HandleShippedState(OrderEvent @event)
{
if (@event == OrderEvent.ConfirmReceipt)
{
Console.WriteLine($"【订单{OrderId}】开始处理确认收货逻辑...");
CompleteOrderSettlement();
SendNotification("订单已完成,感谢您的购买");
CurrentState = OrderState.Completed;
Console.WriteLine($"【订单{OrderId}】状态已从{OrderState.Shipped}→{OrderState.Completed}");
return true;
}
Console.WriteLine($"【订单{OrderId}】已发货状态无法处理事件{@event}");
return false;
}
/// <summary>
/// 处理"已完成"状态的转移逻辑(终态,无后续转移)
/// </summary>
private bool HandleCompletedState(OrderEvent @event)
{
Console.WriteLine($"【订单{OrderId}】订单已完成,无法处理任何事件{@event}");
return false;
}
/// <summary>
/// 处理"已取消"状态的转移逻辑(终态,无后续转移)
/// </summary>
private bool HandleCancelledState(OrderEvent @event)
{
Console.WriteLine($"【订单{OrderId}】订单已取消,无法处理任何事件{@event}");
return false;
}
#endregion
#region 订单相关动作(业务逻辑)
private void GenerateLogisticsNo()
{
Console.WriteLine($"【订单{OrderId}】生成物流单号:LOG{Guid.NewGuid():N.Substring(0, 10)}");
}
private void SendNotification(string message)
{
Console.WriteLine($"【订单{OrderId}】发送通知:{message}");
}
private void ReleaseStock()
{
Console.WriteLine($"【订单{OrderId}】释放商品库存成功");
}
private void UpdateLogisticsInfo(string logisticsInfo)
{
Console.WriteLine($"【订单{OrderId}】更新物流信息:{logisticsInfo}");
}
private void CompleteOrderSettlement()
{
Console.WriteLine($"【订单{OrderId}】完成订单结算,交易关闭");
}
#endregion
}
测试代码(使用状态机)
cs
class Program
{
static void Main(string[] args)
{
// 1. 创建简单订单状态机实例
var orderStateMachine = new SimpleOrderStateMachine("ORDER_20260112_001");
// 2. 按业务流程触发事件,驱动状态转移
Console.WriteLine("===== 第一步:用户支付订单 =====");
orderStateMachine.TriggerEvent(OrderEvent.PaySuccess);
Console.WriteLine("\n===== 第二步:商家发货 =====");
orderStateMachine.TriggerEvent(OrderEvent.ShipGoods);
Console.WriteLine("\n===== 第三步:用户确认收货 =====");
orderStateMachine.TriggerEvent(OrderEvent.ConfirmReceipt);
Console.WriteLine("\n===== 第四步:尝试在已完成状态下取消订单(非法操作) =====");
orderStateMachine.TriggerEvent(OrderEvent.CancelOrder);
Console.ReadKey();
}
}
运行结果
cs
===== 第一步:用户支付订单 =====
【订单ORDER_20260112_001】开始处理支付成功逻辑...
【订单ORDER_20260112_001】生成物流单号:LOGa1b2c3d4e5
【订单ORDER_20260112_001】发送通知:支付成功,订单已确认
【订单ORDER_20260112_001】状态已从Pending→Paid
===== 第二步:商家发货 =====
【订单ORDER_20260112_001】开始处理商家发货逻辑...
【订单ORDER_20260112_001】更新物流信息:顺丰速运:SF1234567890
【订单ORDER_20260112_001】发送通知:商家已发货,请注意查收
【订单ORDER_20260112_001】状态已从Paid→Shipped
===== 第三步:用户确认收货 =====
【订单ORDER_20260112_001】开始处理确认收货逻辑...
【订单ORDER_20260112_001】完成订单结算,交易关闭
【订单ORDER_20260112_001】发送通知:订单已完成,感谢您的购买
【订单ORDER_20260112_001】状态已从Shipped→Completed
===== 第四步:尝试在已完成状态下取消订单(非法操作) =====
【订单ORDER_20260112_001】订单已完成,无法处理任何事件CancelOrder
方式 2:面向对象状态机(基于状态模式,适合复杂场景)
这种方式是状态模式(State Pattern) 的典型应用,将每个状态封装为独立的类,状态的转移逻辑和动作都内聚在对应状态类中,符合 "单一职责原则",扩展性更强(新增状态只需新增一个状态类,无需修改原有代码)。
步骤 1:定义状态接口(统一所有状态的行为)
cs
/// <summary>
/// 订单状态接口(统一所有订单状态的行为)
/// </summary>
public interface IOrderState
{
/// <summary>
/// 处理当前状态下的事件,驱动状态转移
/// </summary>
/// <param name="orderContext">订单上下文(持有状态机的核心信息)</param>
/// <param name="event">触发事件</param>
void Handle(OrderContext orderContext, OrderEvent @event);
}
步骤 2:定义订单上下文(封装状态机的公共信息和状态切换)
cs
/// <summary>
/// 订单上下文(状态机的载体,持有当前状态和公共信息)
/// </summary>
public class OrderContext
{
// 当前订单状态(面向接口编程)
public IOrderState CurrentState { get; set; }
// 订单编号
public string OrderId { get; }
// 构造函数:初始化上下文
public OrderContext(string orderId)
{
OrderId = orderId;
// 初始状态为待支付
CurrentState = new PendingState();
}
/// <summary>
/// 触发事件,委托给当前状态处理
/// </summary>
/// <param name="event">触发事件</param>
public void TriggerEvent(OrderEvent @event)
{
CurrentState.Handle(this, @event);
}
#region 订单公共动作(供各状态类调用)
public void GenerateLogisticsNo()
{
Console.WriteLine($"【订单{OrderId}】生成物流单号:LOG{Guid.NewGuid():N.Substring(0, 10)}");
}
public void SendNotification(string message)
{
Console.WriteLine($"【订单{OrderId}】发送通知:{message}");
}
public void ReleaseStock()
{
Console.WriteLine($"【订单{OrderId}】释放商品库存成功");
}
public void UpdateLogisticsInfo(string logisticsInfo)
{
Console.WriteLine($"【订单{OrderId}】更新物流信息:{logisticsInfo}");
}
public void CompleteOrderSettlement()
{
Console.WriteLine($"【订单{OrderId}】完成订单结算,交易关闭");
}
#endregion
}
步骤 3:实现各个具体状态类(内聚对应状态的逻辑)
cs
#region 具体状态类
/// <summary>
/// 待支付状态
/// </summary>
public class PendingState : IOrderState
{
public void Handle(OrderContext orderContext, OrderEvent @event)
{
switch (@event)
{
case OrderEvent.PaySuccess:
Console.WriteLine($"【订单{orderContext.OrderId}】开始处理支付成功逻辑...");
orderContext.GenerateLogisticsNo();
orderContext.SendNotification("支付成功,订单已确认");
// 切换到已支付状态
orderContext.CurrentState = new PaidState();
Console.WriteLine($"【订单{orderContext.OrderId}】状态已从待支付→已支付");
break;
case OrderEvent.CancelOrder:
Console.WriteLine($"【订单{orderContext.OrderId}】开始处理取消订单逻辑...");
orderContext.ReleaseStock();
orderContext.SendNotification("订单已取消,库存已释放");
// 切换到已取消状态
orderContext.CurrentState = new CancelledState();
Console.WriteLine($"【订单{orderContext.OrderId}】状态已从待支付→已取消");
break;
default:
Console.WriteLine($"【订单{orderContext.OrderId}】待支付状态无法处理事件{@event}");
break;
}
}
}
/// <summary>
/// 已支付状态
/// </summary>
public class PaidState : IOrderState
{
public void Handle(OrderContext orderContext, OrderEvent @event)
{
if (@event == OrderEvent.ShipGoods)
{
Console.WriteLine($"【订单{orderContext.OrderId}】开始处理商家发货逻辑...");
orderContext.UpdateLogisticsInfo("顺丰速运:SF1234567890");
orderContext.SendNotification("商家已发货,请注意查收");
orderContext.CurrentState = new ShippedState();
Console.WriteLine($"【订单{orderContext.OrderId}】状态已从已支付→已发货");
return;
}
Console.WriteLine($"【订单{orderContext.OrderId}】已支付状态无法处理事件{@event}");
}
}
/// <summary>
/// 已发货状态
/// </summary>
public class ShippedState : IOrderState
{
public void Handle(OrderContext orderContext, OrderEvent @event)
{
if (@event == OrderEvent.ConfirmReceipt)
{
Console.WriteLine($"【订单{orderContext.OrderId}】开始处理确认收货逻辑...");
orderContext.CompleteOrderSettlement();
orderContext.SendNotification("订单已完成,感谢您的购买");
orderContext.CurrentState = new CompletedState();
Console.WriteLine($"【订单{orderContext.OrderId}】状态已从已发货→已完成");
return;
}
Console.WriteLine($"【订单{orderContext.OrderId}】已发货状态无法处理事件{@event}");
}
}
/// <summary>
/// 已完成状态(终态)
/// </summary>
public class CompletedState : IOrderState
{
public void Handle(OrderContext orderContext, OrderEvent @event)
{
Console.WriteLine($"【订单{orderContext.OrderId}】订单已完成,无法处理任何事件{@event}");
}
}
/// <summary>
/// 已取消状态(终态)
/// </summary>
public class CancelledState : IOrderState
{
public void Handle(OrderContext orderContext, OrderEvent @event)
{
Console.WriteLine($"【订单{orderContext.OrderId}】订单已取消,无法处理任何事件{@event}");
}
}
#endregion
步骤 4:测试面向对象状态机
cs
class Program
{
static void Main(string[] args)
{
// 1. 创建订单上下文(状态机载体)
var orderContext = new OrderContext("ORDER_20260112_002");
// 2. 触发事件,驱动状态转移
Console.WriteLine("===== 第一步:用户支付订单 =====");
orderContext.TriggerEvent(OrderEvent.PaySuccess);
Console.WriteLine("\n===== 第二步:商家发货 =====");
orderContext.TriggerEvent(OrderEvent.ShipGoods);
Console.WriteLine("\n===== 第三步:用户确认收货 =====");
orderContext.TriggerEvent(OrderEvent.ConfirmReceipt);
Console.WriteLine("\n===== 第四步:尝试在已完成状态下取消订单(非法操作) =====");
orderContext.TriggerEvent(OrderEvent.CancelOrder);
Console.ReadKey();
}
}
运行结果(与简单状态机一致,扩展性更强)
cs
===== 第一步:用户支付订单 =====
【订单ORDER_20260112_002】开始处理支付成功逻辑...
【订单ORDER_20260112_002】生成物流单号:LOGf6g7h8i9j0
【订单ORDER_20260112_002】发送通知:支付成功,订单已确认
【订单ORDER_20260112_002】状态已从待支付→已支付
===== 第二步:商家发货 =====
【订单ORDER_20260112_002】开始处理商家发货逻辑...
【订单ORDER_20260112_002】更新物流信息:顺丰速运:SF1234567890
【订单ORDER_20260112_002】发送通知:商家已发货,请注意查收
【订单ORDER_20260112_002】状态已从已支付→已发货
===== 第三步:用户确认收货 =====
【订单ORDER_20260112_002】开始处理确认收货逻辑...
【订单ORDER_20260112_002】完成订单结算,交易关闭
【订单ORDER_20260112_002】发送通知:订单已完成,感谢您的购买
【订单ORDER_20260112_002】状态已从已发货→已完成
===== 第四步:尝试在已完成状态下取消订单(非法操作) =====
【订单ORDER_20260112_002】订单已完成,无法处理任何事件CancelOrder
三、两种实现方式的对比与适用场景
|------------------------|----------------------------------|-----------------------------------|--------------------------------------------------|
| 实现方式 | 优点 | 缺点 | 适用场景 |
| 简单状态机(Dictionary + 委托) | 实现简单、轻量级、无需额外类结构、开发效率高 | 扩展性差(新增状态需修改字典配置和转移逻辑)、复杂场景下逻辑易混乱 | 状态数量少(5 个以内)、转移规则简单、无需频繁扩展的场景 |
| 面向对象状态机(状态模式) | 扩展性强(新增状态只需新增类)、逻辑内聚、可读性高、符合开闭原则 | 实现复杂、类数量多、有一定学习成本 | 状态数量多、转移规则复杂、需要频繁扩展(新增状态 / 事件)的企业级场景(如电商、物流、工作流) |
四、C# 中的高级状态机
对于超复杂的状态机场景(如带状态历史、并发状态、子状态机),无需重复造轮子,可以使用成熟的第三方库:
- Stateless :轻量级、开源的.NET 状态机库,支持流畅 API、委托、异步动作、状态持久化等,NuGet 安装:
Install-Package Stateless - Automatonymous :基于 MassTransit 的事件驱动状态机库,支持复杂的异步流转和分布式场景,NuGet 安装:
Install-Package Automatonymous
总结
- 状态机是描述对象有限状态、转移规则、事件和动作的模型,核心价值是解决复杂状态切换的逻辑混乱;
- 状态机的 4 个核心要素:状态(State)、事件(Event)、转移(Transition)、动作(Action);
- C# 中简单场景可使用
Dictionary+委托实现轻量级状态机,复杂场景推荐基于状态模式的面向对象实现; - 状态机的核心思想是 "状态驱动行为",通过明确的转移规则减少非法状态,提高代码的可维护性和扩展性。