发布于2026-07-17 阅读(0)
扫一扫,手机访问
状态机这个工具,在C#里到底该怎么选型,一直是不少开发者纠结的问题。有人习惯用一堆if-else或switch来切状态,短期看确实能跑通,但一旦需要新增一条“暂停中转到错误”的路径,边界判断就容易遗漏。要想稳,得先分清场景:switch适合状态少、跳转逻辑直白(比如解析器的token状态),且不涉及状态生命周期管理;而StatePattern是为“状态自身携带行为,可复用,且需要隔离副作用”准备的——比如一个网络连接对象,Connected状态下允许发包,而Disconnected下调用Send()就应该直接抛异常,而不是靠上层反复判断if (state == Connected)。

switch 还是 StatePattern?设计模式不是用来硬套的。3个状态以内、没有异步等待、没有状态进入/退出钩子,switch更轻量;一旦出现“某个状态要持有一个定时器”、“状态间传递上下文对象”、“需要单元测试单个状态行为”这类需求,就立刻切到StatePattern。
StateMachine(如 Stateless)为什么上线后总出诡异跳转?用Stateless库时,最常踩的坑不是配置错误,而是忽略了触发器(TTrigger)的不可变性。如果传入的是一个可变对象(比如class NetworkEvent { public string Code; }),并在触发后修改了Code,后续状态检查很可能基于已被污染的值——因为Stateless默认按引用缓存触发器实例做匹配。
enum或string做TTrigger,避免自定义类Configure(State).Permit(Trigger, NextState),不要依赖隐式发现OnTransitioned回调里打日志,但要注意:回调发生在状态已变更之后,想拦截非法跳转需要用OnEntry/OnExit + 抛异常Fire()会导致状态撕裂——必须自己加lock或使用AsyncLock包一层Task 生命周期怎么跟状态对齐?C# 编译器生成的 async 状态机和业务状态机是两套东西,强行绑定很容易出问题。典型错误是:在OnEntry里启动一个Task,然后以为“等它完成状态才真正就绪”,结果外部代码已经根据新状态执行下一步了。
正确做法只有一条:状态变更必须是同步的,异步动作只能作为状态的副作用,且不能阻塞状态流转。
OnEntry启动后台任务时,用Task.Run(() => {...}).Forget()(需引用Microsoft.Extensions.DependencyInjection的扩展)或手动捕获异常,但绝不awaitConnecting →(异步完成)→ Connected,中间用事件或回调驱动第二次Fire()OnExit里await,这会让状态机卡住——改用同步清理资源,异步部分走独立管道goto 和 while + switch 哪个更可控?别被“goto有害论”带偏了。在极简嵌入式协议解析、高性能报文分发等场景,手写状态机用goto反而最干净——没有循环开销,跳转目标明确,JIT也优化得不错。但前提是:状态数固定、无动态加载、不需要调试可视化。
ParseLoop: switch (currentByte) { case 0xFF: state = State.Header; goto ParseLoop; case 0x00: state = State.Payload; goto ParseLoop; default: goto Error; }Error: // 处理错误而while + switch更易读,支持break/continue,方便插桩测覆盖率,但每次循环都有条件判断开销。如果状态跳转频繁(比如每微秒处理一个字节),goto能省下几个cycle;日常业务代码,选while + switch更稳妥——毕竟人比CPU容易出错。
真正难的从来不是语法选择,而是状态迁移图有没有画全,特别是所有error → idle、timeout → retry的退路有没有覆盖。漏掉一条,线上就多一个stuck状态。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8