C#中的简单状态机示例?

Jen*_*ens 244 c# state-machine

更新:

再次感谢这些例子,他们非常有帮助,以下我并不是要从他们身上拿走任何东西.

就我理解它们和状态机而言,目前给出的例子不是我们通常理解的状态机的一半吗?
在某种意义上,示例确实改变了状态,但这只是通过改变变量的值来表示(并允许在不同的状态中允许不同的值变化),而通常状态机也应该改变它的行为,并且行为不是(仅)在根据状态允许变量的不同值更改的意义,但是允许针对不同状态执行不同方法.

或者我对状态机及其常见用途存在误解?

最好的祝福


原始问题:

在c#中找到了关于状态机和迭代器块的讨论以及创建状态机的工具以及C#没有的东西,所以我发现了许多抽象的东西,但作为一个菜鸟,所有这些都有点令人困惑.

因此,如果有人可以提供一个C#源代码示例,它可以实现一个简单的状态机,可能有3,4个状态,只是为了得到它的要点.


Jul*_*iet 398

让我们从这个简单的状态图开始:

简单的状态机图

我们有:

  • 4个状态(非活动,活动,暂停和退出)
  • 5种类型的状态转换(开始命令,结束命令,暂停命令,恢复命令,退出命令).

您可以通过多种方式将其转换为C#,例如在当前状态和命令上执行switch语句,或在转换表中查找转换.对于这个简单的状态机,我更喜欢转换表,使用以下代码很容易表示Dictionary:

using System;
using System.Collections.Generic;

namespace Juliet
{
    public enum ProcessState
    {
        Inactive,
        Active,
        Paused,
        Terminated
    }

    public enum Command
    {
        Begin,
        End,
        Pause,
        Resume,
        Exit
    }

    public class Process
    {
        class StateTransition
        {
            readonly ProcessState CurrentState;
            readonly Command Command;

            public StateTransition(ProcessState currentState, Command command)
            {
                CurrentState = currentState;
                Command = command;
            }

            public override int GetHashCode()
            {
                return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                StateTransition other = obj as StateTransition;
                return other != null && this.CurrentState == other.CurrentState && this.Command == other.Command;
            }
        }

        Dictionary<StateTransition, ProcessState> transitions;
        public ProcessState CurrentState { get; private set; }

        public Process()
        {
            CurrentState = ProcessState.Inactive;
            transitions = new Dictionary<StateTransition, ProcessState>
            {
                { new StateTransition(ProcessState.Inactive, Command.Exit), ProcessState.Terminated },
                { new StateTransition(ProcessState.Inactive, Command.Begin), ProcessState.Active },
                { new StateTransition(ProcessState.Active, Command.End), ProcessState.Inactive },
                { new StateTransition(ProcessState.Active, Command.Pause), ProcessState.Paused },
                { new StateTransition(ProcessState.Paused, Command.End), ProcessState.Inactive },
                { new StateTransition(ProcessState.Paused, Command.Resume), ProcessState.Active }
            };
        }

        public ProcessState GetNext(Command command)
        {
            StateTransition transition = new StateTransition(CurrentState, command);
            ProcessState nextState;
            if (!transitions.TryGetValue(transition, out nextState))
                throw new Exception("Invalid transition: " + CurrentState + " -> " + command);
            return nextState;
        }

        public ProcessState MoveNext(Command command)
        {
            CurrentState = GetNext(command);
            return CurrentState;
        }
    }


    public class Program
    {
        static void Main(string[] args)
        {
            Process p = new Process();
            Console.WriteLine("Current State = " + p.CurrentState);
            Console.WriteLine("Command.Begin: Current State = " + p.MoveNext(Command.Begin));
            Console.WriteLine("Command.Pause: Current State = " + p.MoveNext(Command.Pause));
            Console.WriteLine("Command.End: Current State = " + p.MoveNext(Command.End));
            Console.WriteLine("Command.Exit: Current State = " + p.MoveNext(Command.Exit));
            Console.ReadLine();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

作为个人喜好,我喜欢设计我的状态机,其GetNext功能是确定性地返回下一个状态,以及MoveNext改变状态机的功能.

  • +1使用素数正确实现`GetHashCode()`. (61认同)
  • @Siddharth:`StateTransition`类用作字典中的键,键的相等性很重要."StateTransition"的两个不同实例应该被认为是相等的,只要它们代表相同的转换(例如`CurrentState`和`Command`是相同的).要实现相等,你必须覆盖`Equals`和`GetHashCode`.特别是字典将使用哈希码,并且两个相等的对象必须返回相同的哈希码.如果没有太多不相等的对象共享相同的哈希码,那么你也会获得良好的性能,这就是为什么`GetHashCode`如图所示实现的原因. (14认同)
  • 虽然这肯定会让你成为状态机(以及适当的C#'ish实现),但我觉得它仍然缺少OP关于改变行为的问题的答案?毕竟,它只是计算状态,但是与状态变化相关的行为,程序的实际内容以及通常称为进入/退出事件,仍然缺失. (13认同)
  • 你能解释一下GetHashCode()的目的吗? (12认同)
  • @FBryant87,如果您使用现代 C#,您可以执行以下操作:`GetHashCode() =&gt; (Command, State).GetHashCode()` 类似的东西。现在简单多了! (3认同)
  • 如果有人需要它:我调整了此套针机,并将其用于我的团结游戏中。它可以在git hub上获得:https://github.com/MarcoMig/Finite-State-Machine-FSM (2认同)
  • 感谢您提供干净的代码,但是我有一个简单的问题。如果进入某种状态时需要执行特定操作,我将如何使用此示例来实现它?非常感谢。 (2认同)
  • @Mazyod:如果有溢出,它会环绕.这个哈希码实现是非常标准的,虽然我不喜欢OP使用了31次; 这可能会造成比必要的更多碰撞.. (2认同)
  • @Max_Power89 你的“调整”不多?您真的可以通过在 Git 上发布该设计来声称自己是该设计的所有者吗? (2认同)

Rem*_*oor 70

您可能希望使用现有的开源有限状态机之一.例如,在http://code.google.com/p/bbvcommon/wiki/StateMachine上找到了bbv.Common.StateMachine .它具有非常直观的流畅语法和许多功能,例如进入/退出操作,转换操作,警卫,分层,被动实现(在调用者的线程上执行)和活动实现(fsm运行的自己的线程,事件被添加到队列中).

以Juliets为例,状态机的定义变得非常简单:

var fsm = new PassiveStateMachine<ProcessState, Command>();
fsm.In(ProcessState.Inactive)
   .On(Command.Exit).Goto(ProcessState.Terminated).Execute(SomeTransitionAction)
   .On(Command.Begin).Goto(ProcessState.Active);
fsm.In(ProcessState.Active)
   .ExecuteOnEntry(SomeEntryAction)
   .ExecuteOnExit(SomeExitAction)
   .On(Command.End).Goto(ProcessState.Inactive)
   .On(Command.Pause).Goto(ProcessState.Paused);
fsm.In(ProcessState.Paused)
   .On(Command.End).Goto(ProcessState.Inactive).OnlyIf(SomeGuard)
   .On(Command.Resume).Goto(ProcessState.Active);
fsm.Initialize(ProcessState.Inactive);
fsm.Start();

fsm.Fire(Command.Begin);
Run Code Online (Sandbox Code Playgroud)

更新:项目位置已移至:https://github.com/appccelerate/statemachine

  • 感谢您参考这个优秀的开源状态机.我可以问一下如何获得当前状态? (4认同)
  • 问题是你"需要"什么,如果你真的需要SM状态或其他类型的状态.例如,如果您需要一些显示文本,那么几个声明可以具有相同的显示文本,例如,如果准备发送具有多个子状态.在这种情况下,您应该完全按照自己的意愿行事.在正确的位置更新一些显示文本.例如,在ExecuteOnEntry中.如果您需要更多信息,请询问一个新问题,并准确说明您的问题,因为这是在这里的主题. (4认同)
  • +1用于流畅和声明性API.这很棒.顺便说一句,谷歌代码似乎已经过时了.他们最新的项目网站是在GitHub [这里](https://github.com/appccelerate/statemachine) (4认同)
  • 你不能,也不应该.国家是不稳定的.当您请求状态时,您可能正处于转换过程中.所有操作都应在转换,状态输入和状态退出中完成.如果您确实想拥有状态,则可以添加本地字段并在输入操作中分配状态. (2认同)

Pet*_*nes 48

这是一个非常经典的有限状态机的例子,建模一个非常简化的电子设备(如电视)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace fsm
{
class Program
{
    static void Main(string[] args)
    {
        var fsm = new FiniteStateMachine();
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.PlugIn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOff);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.RemovePower);
        Console.WriteLine(fsm.State);
        Console.ReadKey();
    }

    class FiniteStateMachine
    {
        public enum States { Start, Standby, On };
        public States State { get; set; }

        public enum Events { PlugIn, TurnOn, TurnOff, RemovePower };

        private Action[,] fsm;

        public FiniteStateMachine()
        {
            this.fsm = new Action[3, 4] { 
                //PlugIn,       TurnOn,                 TurnOff,            RemovePower
                {this.PowerOn,  null,                   null,               null},              //start
                {null,          this.StandbyWhenOff,    null,               this.PowerOff},     //standby
                {null,          null,                   this.StandbyWhenOn, this.PowerOff} };   //on
        }
        public void ProcessEvent(Events theEvent)
        {
            this.fsm[(int)this.State, (int)theEvent].Invoke();
        }

        private void PowerOn() { this.State = States.Standby; }
        private void PowerOff() { this.State = States.Start; }
        private void StandbyWhenOn() { this.State = States.Standby; }
        private void StandbyWhenOff() { this.State = States.On; }
    }
}
}
Run Code Online (Sandbox Code Playgroud)

  • 对于任何刚接触状 (5认同)
  • 我是国家机器的新手并且认真对待,这给我带来了光明 - 谢谢! (2认同)
  • 我喜欢这个实现。对于任何可能偶然发现这一点的人来说,这是一个轻微的“改进”。在 FSM 类中,我添加了 `private void DoNothing() {return;}` 并用 `this.DoNothing` 替换了所有 null 实例。具有返回当前状态的令人愉快的副作用。 (2认同)
  • 我想知道其中一些名称背后是否有原因。当我看到这个时,我的第一直觉是将“State”的元素重命名为“Unpowered、Standby、On”。我的理由是,如果有人问我电视处于什么状态,我会说“关闭”而不是“开始”。我还将“StandbyWhenOn”和“StandbyWhenOff”更改为“TurnOn”和“TurnOff”。这使得代码读起来更直观,但我想知道是否有约定或其他因素使我的术语不太合适。 (2认同)

skr*_*bel 20

这里有一些无耻的自我宣传,但不久前我创建了一个名为YieldMachine的库,它允许以非常简洁的方式描述有限复杂度的状态机.例如,考虑一盏灯:

一台灯的状态机

请注意,此状态机有2个触发器和3个状态.在YieldMachine代码中,我们为所有与状态相关的行为编写了一个方法,其中我们goto为每个状态提交了可怕的使用狂.触发器成为类型的属性或字段Action,使用名为的属性进行修饰Trigger.我已经评论了第一个状态的代码及其转换如下; 接下来的州遵循相同的模式.

public class Lamp : StateMachine
{
    // Triggers (or events, or actions, whatever) that our
    // state machine understands.
    [Trigger]
    public readonly Action PressSwitch;

    [Trigger]
    public readonly Action GotError;

    // Actual state machine logic
    protected override IEnumerable WalkStates()
    {
    off:                                       
        Console.WriteLine("off.");
        yield return null;

        if (Trigger == PressSwitch) goto on;
        InvalidTrigger();

    on:
        Console.WriteLine("*shiiine!*");
        yield return null;

        if (Trigger == GotError) goto error;
        if (Trigger == PressSwitch) goto off;
        InvalidTrigger();

    error:
        Console.WriteLine("-err-");
        yield return null;

        if (Trigger == PressSwitch) goto off;
        InvalidTrigger();
    }
}
Run Code Online (Sandbox Code Playgroud)

简短又好看,呃!

只需向其发送触发器即可控制此状态机:

var sm = new Lamp();
sm.PressSwitch(); //go on
sm.PressSwitch(); //go off

sm.PressSwitch(); //go on
sm.GotError();    //get error
sm.PressSwitch(); //go off
Run Code Online (Sandbox Code Playgroud)

为了澄清,我在第一个州添加了一些注释,以帮助您了解如何使用它.

    protected override IEnumerable WalkStates()
    {
    off:                                       // Each goto label is a state

        Console.WriteLine("off.");             // State entry actions

        yield return null;                     // This means "Wait until a 
                                               // trigger is called"

                                               // Ah, we got triggered! 
                                               //   perform state exit actions 
                                               //   (none, in this case)

        if (Trigger == PressSwitch) goto on;   // Transitions go here: 
                                               // depending on the trigger 
                                               // that was called, go to
                                               // the right state

        InvalidTrigger();                      // Throw exception on 
                                               // invalid trigger

        ...
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为C#编译器实际上为每个使用的方法在内部创建了一个状态机yield return.这个构造通常用于懒惰地创建数据序列,但在这种情况下,我们实际上并不对返回的序列感兴趣(无论如何都是null),而是在引擎盖下创建的状态行为.

所述StateMachine基类确实上建设一些反射来分配代码到每个[Trigger]动作,设定Trigger构件和所述状态机向前移动.

但是你真的不需要理解内部能够使用它.

  • @Brannon:哪种语言允许`goto`在方法之间跳转?我不知道这可能会如何运作.不,`goto`是有问题的,因为它会导致程序编程(这本身就会使单元测试变得很复杂),促进代码重复(注意到如何为每个状态插入`InvalidTrigger`)并最终使程序更加流程化跟随.将此与此线程中的(大多数)其他解决方案进行比较,您将看到这是唯一一个整个FSM在单个方法中发生的解决方案.这通常足以引起关注. (3认同)
  • 如果它在方法之间跳转,那么"goto"只是残暴的.幸运的是,C#中不允许这样做. (2认同)
  • @Groo,例如 GW-BASIC。它没有方法,甚至没有函数,这很有帮助。除此之外,我很难理解为什么在这个例子中你发现“程序流更难遵循”。这是一个状态机,从另一个状态“进入”一个状态是你唯一要做的事情。这很好地映射到 `goto`。 (2认同)
  • GW-BASIC允许`goto`在函数之间跳转,但它不支持函数?:)你是对的,"更难以遵循"的评论更像是一个普通的"转到"问题,在这种情况下确实没有那么大的问题. (2认同)

Kev*_*Hsu 11

您可以编写迭代器块代码,以便以协调的方式执行代码块.代码块是如何分解的,实际上不必对应任何东西,它只是你想要编码的方式.例如:

IEnumerable<int> CountToTen()
{
    System.Console.WriteLine("1");
    yield return 0;
    System.Console.WriteLine("2");
    System.Console.WriteLine("3");
    System.Console.WriteLine("4");
    yield return 0;
    System.Console.WriteLine("5");
    System.Console.WriteLine("6");
    System.Console.WriteLine("7");
    yield return 0;
    System.Console.WriteLine("8");
    yield return 0;
    System.Console.WriteLine("9");
    System.Console.WriteLine("10");
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,当您调用CountToTen时,实际上没有执行任何操作.你得到的实际上是一个状态机生成器,你可以为它创建一个状态机的新实例.您可以通过调用GetEnumerator()来完成此操作.生成的IEnumerator实际上是一个状态机,您可以通过调用MoveNext(...)来驱动它.

因此,在这个例子中,第一次调用MoveNext(...)时,您会看到"1"写入控制台,下次调用MoveNext(...)时,您将看到2,3,4和然后是5,6,7和8,然后是9,10.正如你所看到的,它是一种有用的机制,用于协调事情应该如何发生.

  • 与[公平警告]的强制性联系(http://stackoverflow.com/questions/1194853/implementing-a-state-machine-using-the-yield-keyword/1195205#1195205) (6认同)

Pet*_*nes 8

我在这里发布了另一个答案,因为这是来自不同角度的状态机; 非常直观.

我的原始答案是clasic不完整的代码.我认为它的代码非常直观,因为数组使得状态机的可视化变得简单.缺点是你必须写下这一切. Remos的回答缓解了编写样板代码的努力,但远没有那么直观.还有第三种选择; 真的画国家机器.

如果您使用的是.NET并且可以定位运行时的第4版,那么您可以选择使用工作流的状态机活动.这些本质上是让你绘制状态机(就像朱丽叶的图中一样)并让WF运行时为你执行它.

有关更多详细信息,请参阅MSDN文章使用Windows Workflow Foundation构建状态机,以及此CodePlex站点获取最新版本.

这是我在定位.NET时总是喜欢的选项,因为它很容易看到,改变并向非程序员解释; 他们说的图片胜过千言万语!


Sku*_*del 7

记住状态机是一个抽象是很有用的,你不需要特殊的工具来创建一个,但是工具可能很有用.

例如,您可以实现具有以下功能的状态机:

void Hunt(IList<Gull> gulls)
{
    if (gulls.Empty())
       return;

    var target = gulls.First();
    TargetAcquired(target, gulls);
}

void TargetAcquired(Gull target, IList<Gull> gulls)
{
    var balloon = new WaterBalloon(weightKg: 20);

    this.Cannon.Fire(balloon);

    if (balloon.Hit)
    {
       TargetHit(target, gulls);
    }
    else
       TargetMissed(target, gulls);
}

void TargetHit(Gull target, IList<Gull> gulls)
{
    Console.WriteLine("Suck on it {0}!", target.Name);
    Hunt(gulls);
}

void TargetMissed(Gull target, IList<Gull> gulls)
{
    Console.WriteLine("I'll get ya!");
    TargetAcquired(target, gulls);
}
Run Code Online (Sandbox Code Playgroud)

这台机器会搜寻海鸥并试图用水气球打它们.如果它未命中它将尝试触发它直到它击中(可以做一些现实的期望;)),否则它将在控制台中幸灾乐祸.它继续捕猎,直到它被海鸥骚扰.

每个功能对应于每个状态; 未显示开始和结束(或接受)状态.那里可能有更多的状态,而不是由函数建模.例如,在点燃气球之后,机器真的处于与之前相比的另一种状态,但我认为这种区别是不切实际的.

一种常见的方法是使用类来表示状态,然后以不同的方式连接它们.


zzf*_*ima 6

今天我深入国家设计模式.我做了并测试了ThreadState,它与C#中的线程相等(+/-),如C#中的线程图所示

在此输入图像描述

您可以轻松添加新状态,配置从一个状态到另一个状态的移动非常容易,因为它在状态实现中封装

实现和使用at:通过State Design Pattern实现.NET ThreadState

  • 链接已死。你还有吗? (2认同)

Jet*_*lue 6

在网上找到这个很棒的教程,它帮助我绕过有限状态机.

http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867

该教程与语言无关,因此可以轻松地适应您的C#需求.

此外,使用的例子(寻找食物的蚂蚁)很容易理解.


从教程:

在此输入图像描述

public class FSM {
    private var activeState :Function; // points to the currently active state function

    public function FSM() {
    }

    public function setState(state :Function) :void {
        activeState = state;
    }

    public function update() :void {
        if (activeState != null) {
            activeState();
        }
    }
}


public class Ant
{
    public var position   :Vector3D;
    public var velocity   :Vector3D;
    public var brain      :FSM;

    public function Ant(posX :Number, posY :Number) {
        position    = new Vector3D(posX, posY);
        velocity    = new Vector3D( -1, -1);
        brain       = new FSM();

        // Tell the brain to start looking for the leaf.
        brain.setState(findLeaf);
    }

    /**
    * The "findLeaf" state.
    * It makes the ant move towards the leaf.
    */
    public function findLeaf() :void {
        // Move the ant towards the leaf.
        velocity = new Vector3D(Game.instance.leaf.x - position.x, Game.instance.leaf.y - position.y);

        if (distance(Game.instance.leaf, this) <= 10) {
            // The ant is extremelly close to the leaf, it's time
            // to go home.
            brain.setState(goHome);
        }

        if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) {
            // Mouse cursor is threatening us. Let's run away!
            // It will make the brain start calling runAway() from
            // now on.
            brain.setState(runAway);
        }
    }

    /**
    * The "goHome" state.
    * It makes the ant move towards its home.
    */
    public function goHome() :void {
        // Move the ant towards home
        velocity = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y);

        if (distance(Game.instance.home, this) <= 10) {
            // The ant is home, let's find the leaf again.
            brain.setState(findLeaf);
        }
    }

    /**
    * The "runAway" state.
    * It makes the ant run away from the mouse cursor.
    */
    public function runAway() :void {
        // Move the ant away from the mouse cursor
        velocity = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y);

        // Is the mouse cursor still close?
        if (distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) {
            // No, the mouse cursor has gone away. Let's go back looking for the leaf.
            brain.setState(findLeaf);
        }
    }

    public function update():void {
        // Update the FSM controlling the "brain". It will invoke the currently
        // active state function: findLeaf(), goHome() or runAway().
        brain.update();

        // Apply the velocity vector to the position, making the ant move.
        moveBasedOnVelocity();
    }

    (...)
}
Run Code Online (Sandbox Code Playgroud)


Ble*_*ose 6

不确定我是否错过了重点,但我认为这里的答案都不是“简单”的状态机。我通常所说的简单状态机是使用内部带有开关的循环。这就是我们在大学的PLC/微芯片编程或C/C++编程中使用的方法。

优点:

  • 容易写。不需要特殊的物体和东西。你甚至不需要面向对象。
  • 当它很小的时候,就很容易理解。

缺点:

  • 当有很多状态时,可能会变得很大并且难以阅读。

看起来像这样:

public enum State
{
    First,
    Second,
    Third,
}

static void Main(string[] args)
{
    var state = State.First;
    // x and i are just examples for stuff that you could change inside the state and use for state transitions
    var x     = 0; 
    var i     = 0;

    // does not have to be a while loop. you could loop over the characters of a string too
    while (true)  
    {
        switch (state)
        {
            case State.First:
                // Do sth here
                if (x == 2)
                    state = State.Second;  
                    // you may or may not add a break; right after setting the next state
                // or do sth here
                if (i == 3)
                    state = State.Third;
                // or here
                break;
            case State.Second:
                // Do sth here
                if (x == 10)
                    state = State.First;
                // or do sth here
                break;
            case State.Third:
                // Do sth here
                if (x == 10)
                    state = State.First;
                // or do sth here
                break;
            default:
                // you may wanna throw an exception here.
                break;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

如果它确实应该是一个状态机,您可以在其中调用根据您所处的状态做出不同反应的方法:状态设计模式是更好的方法


小智 5

我还没有尝试在C#中实现FSM,但这些听起来(或看起来)非常复杂,因为我过去在C或ASM等低级语言中处理FSM的方式.

我相信我一直都知道的方法被称为"迭代循环".在它中,你基本上有一个'while'循环,它根据事件(中断)定期退出,然后再次返回主循环.

在中断处理程序中,您将传递一个CurrentState并返回一个NextState,然后它将覆盖主循环中的CurrentState变量.在程序关闭(或微控制器重置)之前,您无限制地执行此操作.

我所看到的其他答案看起来都非常复杂,而在我看来,FSM是如何实现的; 它的美丽在于它的简洁性和FSM可能非常复杂,有许多很多状态和转换,但它们允许复杂的过程容易被分解和消化.

我意识到我的回答不应该包括另一个问题,但我不得不问:为什么这些其他提议的解决方案看起来如此复杂?
它们似乎类似于用巨大的大锤击打小钉子.

  • 除非你有一个非常复杂的状态机,有很多状态和条件,否则你最终会有多个嵌套开关.在繁忙等待中也可能会受到惩罚,具体取决于您的循环实现. (2认同)

Biz*_*han 5

我用朱丽叶的代码制作了这个通用状态机。它对我来说非常棒。

这些是好处:

  • 您可以使用两个枚举在代码中创建新的状态机TStateTCommand
  • 添加了结构TransitionResult<TState>以更好地控制[Try]GetNext()方法的输出结果
  • StateTransition 通过AddTransition(TState, TCommand, TState)使其更易于使用来公开嵌套类

代码:

public class StateMachine<TState, TCommand>
    where TState : struct, IConvertible, IComparable
    where TCommand : struct, IConvertible, IComparable
{
    protected class StateTransition<TS, TC>
        where TS : struct, IConvertible, IComparable
        where TC : struct, IConvertible, IComparable
    {
        readonly TS CurrentState;
        readonly TC Command;

        public StateTransition(TS currentState, TC command)
        {
            if (!typeof(TS).IsEnum || !typeof(TC).IsEnum)
            {
                throw new ArgumentException("TS,TC must be an enumerated type");
            }

            CurrentState = currentState;
            Command = command;
        }

        public override int GetHashCode()
        {
            return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            StateTransition<TS, TC> other = obj as StateTransition<TS, TC>;
            return other != null
                && this.CurrentState.CompareTo(other.CurrentState) == 0
                && this.Command.CompareTo(other.Command) == 0;
        }
    }

    private Dictionary<StateTransition<TState, TCommand>, TState> transitions;
    public TState CurrentState { get; private set; }

    protected StateMachine(TState initialState)
    {
        if (!typeof(TState).IsEnum || !typeof(TCommand).IsEnum)
        {
            throw new ArgumentException("TState,TCommand must be an enumerated type");
        }

        CurrentState = initialState;
        transitions = new Dictionary<StateTransition<TState, TCommand>, TState>();
    }

    /// <summary>
    /// Defines a new transition inside this state machine
    /// </summary>
    /// <param name="start">source state</param>
    /// <param name="command">transition condition</param>
    /// <param name="end">destination state</param>
    protected void AddTransition(TState start, TCommand command, TState end)
    {
        transitions.Add(new StateTransition<TState, TCommand>(start, command), end);
    }

    public TransitionResult<TState> TryGetNext(TCommand command)
    {
        StateTransition<TState, TCommand> transition = new StateTransition<TState, TCommand>(CurrentState, command);
        TState nextState;
        if (transitions.TryGetValue(transition, out nextState))
            return new TransitionResult<TState>(nextState, true);
        else
            return new TransitionResult<TState>(CurrentState, false);
    }

    public TransitionResult<TState> MoveNext(TCommand command)
    {
        var result = TryGetNext(command);
        if(result.IsValid)
        {
            //changes state
            CurrentState = result.NewState;
        }
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是 TryGetNext 方法的返回类型:

public struct TransitionResult<TState>
{
    public TransitionResult(TState newState, bool isValid)
    {
        NewState = newState;
        IsValid = isValid;
    }
    public TState NewState;
    public bool IsValid;
}
Run Code Online (Sandbox Code Playgroud)

如何使用:

OnlineDiscountStateMachine这是从泛型类创建一个的方法:

定义其状态的枚举和其命令的OnlineDiscountState枚举。OnlineDiscountCommand

使用这两个枚举定义OnlineDiscountStateMachine从泛型类派生的类

派生构造函数,base(OnlineDiscountState.InitialState)初始状态设置为OnlineDiscountState.InitialState

AddTransition根据需要多次使用

public class OnlineDiscountStateMachine : StateMachine<OnlineDiscountState, OnlineDiscountCommand>
{
    public OnlineDiscountStateMachine() : base(OnlineDiscountState.Disconnected)
    {
        AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Connected);
        AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Error_AuthenticationError);
        AddTransition(OnlineDiscountState.Connected, OnlineDiscountCommand.Submit, OnlineDiscountState.WaitingForResponse);
        AddTransition(OnlineDiscountState.WaitingForResponse, OnlineDiscountCommand.DataReceived, OnlineDiscountState.Disconnected);
    }
}
Run Code Online (Sandbox Code Playgroud)

使用派生状态机

    odsm = new OnlineDiscountStateMachine();
    public void Connect()
    {
        var result = odsm.TryGetNext(OnlineDiscountCommand.Connect);

        //is result valid?
        if (!result.IsValid)
            //if this happens you need to add transitions to the state machine
            //in this case result.NewState is the same as before
            Console.WriteLine("cannot navigate from this state using OnlineDiscountCommand.Connect");

        //the transition was successfull
        //show messages for new states
        else if(result.NewState == OnlineDiscountState.Error_AuthenticationError)
            Console.WriteLine("invalid user/pass");
        else if(result.NewState == OnlineDiscountState.Connected)
            Console.WriteLine("Connected");
        else
            Console.WriteLine("not implemented transition result for " + result.NewState);
    }
Run Code Online (Sandbox Code Playgroud)