状态数≤3、无异步、无进入/退出钩子、跳转逻辑简单且性能敏感时,用switch比statepattern更快更稳;一旦涉及状态行为封装、生命周期管理、上下文传递或需单元测试,则必须切换为statepattern。

别一上来就套 StatePattern,3 个状态以内、没异步、没进入/退出钩子,用 switch 更快更稳。
什么时候该用 switch 状态机?
适合状态少、跳转逻辑直白、不涉及生命周期管理的场景。比如解析器里的 token 状态流转、简单协议的状态判别、UI 按钮的启用/禁用控制。
- 状态数 ≤ 3,且新增状态概率低
- 每个状态下的行为就是几行同步代码,没有定时器、没有资源持有、不需要
OnEntry/OnExit - 状态跳转条件全是本地变量判断(如
if (input == 'A') state = State.B;),不依赖外部事件回调 - 性能敏感:
switch是编译期跳转,无对象分配、无虚调用开销
什么时候必须切到 StatePattern?
一旦出现「状态自身携带行为」或「状态需要被测试/复用」,switch 就开始反噬——漏掉 OnExit 清理、忘记某条跳转路径、单元测试只能打桩整个 Context。
- 某个状态要启动并持有
Timer或CancellationTokenSource - 状态间需传递上下文对象(如
ConnectionContext),而不是靠 Context 类全局暴露字段 - 需要对「断开连接时发心跳包」这种行为单独写单元测试——那它就该是一个
DisconnectedState的方法,而不是散落在switch分支里的 if 块 - 有
Enter()/Exit()钩子,且这些钩子可能抛异常(比如Exit()关 socket 失败,应阻断跳转)
StateMachine<tstate ttrigger></tstate> 最常踩的三个坑
用 Stateless 等库时,出问题往往不是配置错,而是违反了底层契约。
-
TTrigger必须是不可变类型:用enum或string,别传class NetworkEvent—— 后者若在Fire()后被修改,缓存的触发器匹配会失效 - 注册转换必须显式调用
Configure(State).Permit(Trigger, NextState),别依赖库的自动发现机制,它不保证顺序,也不报错 -
OnTransitioned是状态变更「之后」才触发,想拦截非法跳转(比如从Connected直接跳Failed),得在OnEntry里校验前置条件并throw,否则跳转已发生
async/await 和业务状态机混用的致命误区
C# 编译器生成的 async 状态机和你写的业务状态机是两套东西,强行绑定会撕裂控制流。
- 禁止在
OnEntry里await:这会让状态机卡住,后续Fire()可能并发执行,导致状态撕裂 - 禁止认为「状态变了,异步任务就一定完成了」:比如
Connecting → Connected后立刻调用Send(),但实际 TCP 握手还没完——正确做法是拆成Connecting → ConnectingWait → Connected,用回调驱动第二次Fire() - 后台异步动作只能作为副作用:用
Task.Run(() => { ... }).Forget()(需引用Microsoft.Extensions.DependencyInjection),且确保失败不影响状态一致性
真正难的不是写状态类,而是界定「状态变更」和「动作执行」的边界——前者必须原子、同步、可回滚;后者可以异步、可失败、可重试。这个分界线模糊了,所有模式都会变成新 bug 的温床。










