【问题标题】:Simple state machine example in C#?C# 中的简单状态机示例?
【发布时间】:2011-08-20 21:27:00
【问题描述】:

更新:

再次感谢您提供的示例,它们非常有帮助,以下内容,我不是说 拿走他们的任何东西。

目前给出的示例,就我所理解的和状态机而言,难道不是我们通常理解的状态机的一半吗?
从某种意义上说,示例确实会更改状态,但这仅通过更改变量的值来表示(并允许在不同状态下更改不同的值),而通常,状态机也应该更改其行为,而行为不应该(仅)在允许根据状态对变量进行不同值更改的意义上,但在允许对不同状态执行不同方法的意义上。

还是我对状态机及其常见用途有误解?


原问题:

我发现这个讨论是关于 state machines & iterator blocks in c# 和创建状态机的工具以及 C# 之类的东西,所以我发现了很多抽象的东西,但作为一个菜鸟,所有这些都有点令人困惑。

因此,如果有人能提供一个 C# 源代码示例来实现一个可能具有 3,4 个状态的简单状态机,以了解其要点,那就太好了。

【问题讨论】:

  • 您想了解的是一般状态机还是基于迭代器的状态机?
  • 有 .Net Core Stateless 库和示例、DAG 图表等 - 值得回顾:hanselman.com/blog/…

标签: c# state-machine


【解决方案1】:

您可以编写一个迭代器块,让您以一种协调的方式执行代码块。代码块如何分解实际上不必对应任何内容,这只是您想要对其进行编码的方式。例如:

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");
}

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

因此,在本例中,第一次调用 MoveNext(...) 时,您将看到控制台写入“1”,下次调用 MoveNext(...) 时,您将看到 2、3、 4,然后是 5、6、7,然后是 8,然后是 9、10。如您所见,它是一种有用的机制来安排事情的发生方式。

【讨论】:

  • 必须链接到fair warning
  • 令人难以置信和大胆的例子......但它锚定@sehe评论,所以......
【解决方案2】:

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

我们有:

  • 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();
        }
    }
}

出于个人喜好,我喜欢用GetNext 函数来设计状态机来返回下一个状态deterministically,并用MoveNext 函数来改变状态机。

【讨论】:

  • +1 使用素数正确实现GetHashCode()
  • 能否请您解释一下 GetHashCode() 的用途?
  • @Siddharth:StateTransition 类用作字典中的键,键的相等性很重要。 StateTransition 的两个不同实例应该被认为是相等的,只要它们代表相同的转换(例如,CurrentStateCommand 是相同的)。要实现平等,您必须覆盖EqualsGetHashCode。特别是字典将使用哈希码,两个相等的对象必须返回相同的哈希码。如果没有太多不相等的对象共享相同的哈希码,您也可以获得良好的性能,这就是GetHashCode 实现如图所示的原因。
  • 虽然这肯定会给你一个状态机(以及一个适当的 C#'ish 实现),但我觉得它仍然缺少 OP 关于改变行为的问题的答案?毕竟,它只是计算状态,但与状态变化相关的行为,程序的实际内容,通常称为进入/退出事件,仍然缺失。
  • 如果有人需要它:我调整了这台泰特机器并在我的统一游戏中使用它。它在 git hub 上可用:github.com/MarcoMig/Finite-State-Machine-FSM
【解决方案3】:

记住状态机是一种抽象是很有用的,您不需要特定的工具来创建它,但是工具可能很有用。

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

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);
}

这台机器会寻找海鸥并尝试用水气球击中它们。如果它错过了它会尝试发射一个直到它命中(可以做一些现实的期望;)),否则它会在控制台中幸灾乐祸。它会继续捕猎,直到没有海鸥来骚扰。

每个函数对应每个状态;未显示开始和结束(或接受)状态。不过,那里的状态可能比函数建模的要多。例如,在发射气球后,机器实际上处于与之前不同的状态,但我认为做出这种区分是不切实际的。

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

【讨论】:

    【解决方案4】:

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

    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; }
        }
    }
    }
    

    【讨论】:

    • 对于刚接触状态机的任何人来说,这是一个很好的第一个例子,可以先弄湿自己的脚。
    • 我是状态机的新手,说真的,这给我带来了光明 - 谢谢!
    • 我喜欢这个实现。对于任何可能偶然发现这一点的人来说,这是一个轻微的“改进”。在 FSM 类中,我添加了 private void DoNothing() {return;} 并将所有 null 实例替换为 this.DoNothing。具有返回当前状态的令人愉快的副作用。
    • 我想知道这些名称背后是否有原因。当我看到这个时,我的第一个直觉是将States 的元素重命名为Unpowered, Standby, On。我的理由是,如果有人问我电视处于什么状态,我会说“关闭”而不是“开始”。我还将StandbyWhenOnStandbyWhenOff 更改为TurnOnTurnOff。这使代码阅读起来更直观,但我想知道是否有约定或其他因素使我的术语不太合适。
    • 看起来很合理,我并没有真正遵循任何州命名约定;命名为对您建模的任何内容都有意义。
    【解决方案5】:

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

    以朱丽叶为例,状态机的定义非常简单:

    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);
    

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

    【讨论】:

    • 感谢您引用这个优秀的开源状态机。请问如何获取当前状态?
    • 你不能也不应该。状态是不稳定的东西。当您请求状态时,您可能正处于转换中间。所有动作都应该在转换、状态进入和状态退出中完成。如果你真的想拥有状态,那么你可以添加一个本地字段并在入口操作中分配状态。
    • 问题是你“需要”什么以及你是否真的需要 SM 状态或其他某种状态。例如。如果您需要一些显示文本,那么几个声明可能具有相同的显示文本,例如,如果准备发送有多个子状态。在这种情况下,您应该完全按照您的意图去做。在正确的位置更新一些显示文本。例如。在 ExecuteOnEntry 中。如果您需要更多信息,请提出一个新问题并准确说明您的问题,因为这与这里无关。
    • 好的,我要问一个新问题,等待您回复。因为我不认为其他人解决了这个问题,因为你有最好的答案,但提问者仍然没有接受。我将在这里发布问题网址。谢谢。
    • +1 用于流畅的声明式 API。这很棒。顺便说一句,谷歌代码似乎已经过时了。他们最新的项目站点在 GitHub 上 here
    【解决方案6】:

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

    我最初的答案是经典的命令式代码。我认为它在代码中非常直观,因为数组使状态机的可视化变得简单。缺点是你必须写所有这些。 Remos 的回答减轻了编写样板代码的工作量,但视觉效果要差得多。还有第三种选择;真正绘制状态机。

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

    有关详细信息,请参阅 MSDN 文章 Building State Machines with Windows Workflow Foundation,有关最新版本,请参阅 this CodePlex site

    这是我在面向 .NET 时总是更喜欢的选项,因为它易于查看、更改和向非程序员解释;正如他们所说,图片值一千字!

    【讨论】:

    • 我认为状态机是整个工作流基础中最好的部分之一!
    【解决方案7】:

    这里有些无耻的自我宣传,但不久前我创建了一个名为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();
        }
    }
    

    又短又好看,嗯!

    这个状态机只需通过向它发送触发器来控制:

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

    为了澄清,我在第一个状态中添加了一些 cmets 以帮助您了解如何使用它。

        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
    
            ...
    

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

    StateMachine 基类对构造进行了一些反思,以将代码分配给每个 [Trigger] 操作,从而设置 Trigger 成员并向前移动状态机。

    但您实际上并不需要了解内部结构即可使用它。

    【讨论】:

    • “goto”只有在方法之间跳转时才会很糟糕。幸运的是,这在 C# 中是不允许的。
    • 好点!事实上,如果任何静态类型语言能够允许在方法之间使用goto,我会印象深刻。
    • @Brannon:哪种语言允许goto 在方法之间跳转?我不明白这怎么可能奏效。不,goto 是有问题的,因为它会导致程序化编程(这本身会使单元测试等好东西变得复杂),促进代码重复(注意到需要为每个状态插入 InvalidTrigger 吗?)最终使程序流程更加困难跟随。将此与该线程中的(大多数)其他解决方案进行比较,您会发现这是唯一一个整个 FSM 以单一方法发生的解决方案。这通常足以引起关注。
    • @Groo,GW-BASIC,例如。它没有方法,甚至没有函数,这很有帮助。除此之外,我很难理解为什么在这个例子中你发现“程序流程更难遵循”。这是一个状态机,从另一个状态“进入”一个状态是你唯一要做的事情。这很好地映射到goto
    • GW-BASIC 允许goto 在函数之间跳转,但不支持函数? :) 你说得对,“更难理解”这句话更像是一个普遍的goto 问题,在这种情况下确实不是什么大问题。
    【解决方案8】:

    StatePattern 真是太棒了。这符合您的需求吗?

    我认为它的上下文相关,但肯定值得一试。

    http://en.wikipedia.org/wiki/State_pattern

    这让您的州决定去哪里,而不是“对象”类。

    布鲁诺

    【讨论】:

    • 状态模式处理的类可以根据它所处的状态/模式采取不同的行动,它不处理状态之间的转换。
    【解决方案9】:

    我刚刚贡献了这个:

    https://code.google.com/p/ysharp/source/browse/#svn%2Ftrunk%2FStateMachinesPoC

    这是演示直接和间接发送命令的示例之一,状态为 IObserver(of signal),因此响应信号源 IObservable(of signal):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Test
    {
        using Machines;
    
        public static class WatchingTvSampleAdvanced
        {
            // Enum type for the transition triggers (instead of System.String) :
            public enum TvOperation { Plug, SwitchOn, SwitchOff, Unplug, Dispose }
    
            // The state machine class type is also used as the type for its possible states constants :
            public class Television : NamedState<Television, TvOperation, DateTime>
            {
                // Declare all the possible states constants :
                public static readonly Television Unplugged = new Television("(Unplugged TV)");
                public static readonly Television Off = new Television("(TV Off)");
                public static readonly Television On = new Television("(TV On)");
                public static readonly Television Disposed = new Television("(Disposed TV)");
    
                // For convenience, enter the default start state when the parameterless constructor executes :
                public Television() : this(Television.Unplugged) { }
    
                // To create a state machine instance, with a given start state :
                private Television(Television value) : this(null, value) { }
    
                // To create a possible state constant :
                private Television(string moniker) : this(moniker, null) { }
    
                private Television(string moniker, Television value)
                {
                    if (moniker == null)
                    {
                        // Build the state graph programmatically
                        // (instead of declaratively via custom attributes) :
                        Handler<Television, TvOperation, DateTime> stateChangeHandler = StateChange;
                        Build
                        (
                            new[]
                            {
                                new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = stateChangeHandler },
                                new { From = Television.Unplugged, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler },
                                new { From = Television.Off, When = TvOperation.SwitchOn, Goto = Television.On, With = stateChangeHandler },
                                new { From = Television.Off, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler },
                                new { From = Television.Off, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler },
                                new { From = Television.On, When = TvOperation.SwitchOff, Goto = Television.Off, With = stateChangeHandler },
                                new { From = Television.On, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler },
                                new { From = Television.On, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler }
                            },
                            false
                        );
                    }
                    else
                        // Name the state constant :
                        Moniker = moniker;
                    Start(value ?? this);
                }
    
                // Because the states' value domain is a reference type, disallow the null value for any start state value : 
                protected override void OnStart(Television value)
                {
                    if (value == null)
                        throw new ArgumentNullException("value", "cannot be null");
                }
    
                // When reaching a final state, unsubscribe from all the signal source(s), if any :
                protected override void OnComplete(bool stateComplete)
                {
                    // Holds during all transitions into a final state
                    // (i.e., stateComplete implies IsFinal) :
                    System.Diagnostics.Debug.Assert(!stateComplete || IsFinal);
    
                    if (stateComplete)
                        UnsubscribeFromAll();
                }
    
                // Executed before and after every state transition :
                private void StateChange(IState<Television> state, ExecutionStep step, Television value, TvOperation info, DateTime args)
                {
                    // Holds during all possible transitions defined in the state graph
                    // (i.e., (step equals ExecutionStep.LeaveState) implies (not state.IsFinal))
                    System.Diagnostics.Debug.Assert((step != ExecutionStep.LeaveState) || !state.IsFinal);
    
                    // Holds in instance (i.e., non-static) transition handlers like this one :
                    System.Diagnostics.Debug.Assert(this == state);
    
                    switch (step)
                    {
                        case ExecutionStep.LeaveState:
                            var timeStamp = ((args != default(DateTime)) ? String.Format("\t\t(@ {0})", args) : String.Empty);
                            Console.WriteLine();
                            // 'value' is the state value that we are transitioning TO :
                            Console.WriteLine("\tLeave :\t{0} -- {1} -> {2}{3}", this, info, value, timeStamp);
                            break;
                        case ExecutionStep.EnterState:
                            // 'value' is the state value that we have transitioned FROM :
                            Console.WriteLine("\tEnter :\t{0} -- {1} -> {2}", value, info, this);
                            break;
                        default:
                            break;
                    }
                }
    
                public override string ToString() { return (IsConstant ? Moniker : Value.ToString()); }
            }
    
            public static void Run()
            {
                Console.Clear();
    
                // Create a signal source instance (here, a.k.a. "remote control") that implements
                // IObservable<TvOperation> and IObservable<KeyValuePair<TvOperation, DateTime>> :
                var remote = new SignalSource<TvOperation, DateTime>();
    
                // Create a television state machine instance (automatically set in a default start state),
                // and make it subscribe to a compatible signal source, such as the remote control, precisely :
                var tv = new Television().Using(remote);
                bool done;
    
                // Always holds, assuming the call to Using(...) didn't throw an exception (in case of subscription failure) :
                System.Diagnostics.Debug.Assert(tv != null, "There's a bug somewhere: this message should never be displayed!");
    
                // As commonly done, we can trigger a transition directly on the state machine :
                tv.MoveNext(TvOperation.Plug, DateTime.Now);
    
                // Alternatively, we can also trigger transitions by emitting from the signal source / remote control
                // that the state machine subscribed to / is an observer of :
                remote.Emit(TvOperation.SwitchOn, DateTime.Now);
                remote.Emit(TvOperation.SwitchOff);
                remote.Emit(TvOperation.SwitchOn);
                remote.Emit(TvOperation.SwitchOff, DateTime.Now);
    
                done =
                    (
                        tv.
                            MoveNext(TvOperation.Unplug).
                            MoveNext(TvOperation.Dispose) // MoveNext(...) returns null iff tv.IsFinal == true
                        == null
                    );
    
                remote.Emit(TvOperation.Unplug); // Ignored by the state machine thanks to the OnComplete(...) override above
    
                Console.WriteLine();
                Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done);
    
                Console.WriteLine();
                Console.WriteLine("Press any key...");
                Console.ReadKey();
            }
        }
    }
    

    注意:这个例子是相当人为的,主要是为了演示一些正交特性。很少有真正需要通过一个完整的类来实现状态值域本身,使用这样的 CRTP(参见:http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)。

    这是一个更简单且可能更常见的实现用例(使用简单的枚举类型作为状态值域),用于相同的状态机和相同的测试用例:

    https://code.google.com/p/ysharp/source/browse/trunk/StateMachinesPoC/WatchingTVSample.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Test
    {
        using Machines;
    
        public static class WatchingTvSample
        {
            public enum Status { Unplugged, Off, On, Disposed }
    
            public class DeviceTransitionAttribute : TransitionAttribute
            {
                public Status From { get; set; }
                public string When { get; set; }
                public Status Goto { get; set; }
                public object With { get; set; }
            }
    
            // State<Status> is a shortcut for / derived from State<Status, string>,
            // which in turn is a shortcut for / derived from State<Status, string, object> :
            public class Device : State<Status>
            {
                // Executed before and after every state transition :
                protected override void OnChange(ExecutionStep step, Status value, string info, object args)
                {
                    if (step == ExecutionStep.EnterState)
                    {
                        // 'value' is the state value that we have transitioned FROM :
                        Console.WriteLine("\t{0} -- {1} -> {2}", value, info, this);
                    }
                }
    
                public override string ToString() { return Value.ToString(); }
            }
    
            // Since 'Device' has no state graph of its own, define one for derived 'Television' :
            [DeviceTransition(From = Status.Unplugged, When = "Plug", Goto = Status.Off)]
            [DeviceTransition(From = Status.Unplugged, When = "Dispose", Goto = Status.Disposed)]
            [DeviceTransition(From = Status.Off, When = "Switch On", Goto = Status.On)]
            [DeviceTransition(From = Status.Off, When = "Unplug", Goto = Status.Unplugged)]
            [DeviceTransition(From = Status.Off, When = "Dispose", Goto = Status.Disposed)]
            [DeviceTransition(From = Status.On, When = "Switch Off", Goto = Status.Off)]
            [DeviceTransition(From = Status.On, When = "Unplug", Goto = Status.Unplugged)]
            [DeviceTransition(From = Status.On, When = "Dispose", Goto = Status.Disposed)]
            public class Television : Device { }
    
            public static void Run()
            {
                Console.Clear();
    
                // Create a television state machine instance, and return it, set in some start state :
                var tv = new Television().Start(Status.Unplugged);
                bool done;
    
                // Holds iff the chosen start state isn't a final state :
                System.Diagnostics.Debug.Assert(tv != null, "The chosen start state is a final state!");
    
                // Trigger some state transitions with no arguments
                // ('args' is ignored by this state machine's OnChange(...), anyway) :
                done =
                    (
                        tv.
                            MoveNext("Plug").
                            MoveNext("Switch On").
                            MoveNext("Switch Off").
                            MoveNext("Switch On").
                            MoveNext("Switch Off").
                            MoveNext("Unplug").
                            MoveNext("Dispose") // MoveNext(...) returns null iff tv.IsFinal == true
                        == null
                    );
    
                Console.WriteLine();
                Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done);
    
                Console.WriteLine();
                Console.WriteLine("Press any key...");
                Console.ReadKey();
            }
        }
    }
    

    'HTH

    【讨论】:

    • 每个状态实例都有自己的状态图副本是不是有点奇怪?
    • @Groo :不,他们没有。只有使用私有构造函数和名称对象的空字符串(因此,调用受保护的“构建”方法)构造的电视实例才会具有状态图,作为状态机。其他的,命名为 Television 的实例(对于传统和临时目的,具有一个绰号 not null)将仅仅是“固定点”状态(可以这么说),作为状态常量(即实际状态机的状态图将作为它们的顶点引用)。 'HTH,
    • 好的,我明白了。无论如何,恕我直言,如果您包含一些实际处理这些转换的代码会更好。这样,它仅用作为您的库使用(恕我直言)不太明显的界面的示例。比如StateChange是怎么解析的?通过反射?真的有必要吗?
    • @Groo :好话。确实没有必要在第一个示例中反映处理程序,因为它是在其中以编程方式精确完成的,并且可以静态绑定/类型检查(与通过自定义属性时不同)。所以这项工作也按预期工作:private Television(string moniker, Television value) { Handler&lt;Television, TvOperation, DateTime&gt; myHandler = StateChange; // (code omitted) new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = myHandler } }
    • 感谢您的努力!
    【解决方案10】:

    今天我深入研究了状态设计模式。 我做了并测试了 ThreadState,它等于 (+/-) 到 C# 中的线程,如Threading in C# 中的图片中所述

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

    实现和使用在:Implements .NET ThreadState by State Design Pattern

    【讨论】:

    • 链接已失效。你还有别的吗?
    【解决方案11】:

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

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

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

    在我看来,与 FSM 的实现方式相比,我看到的其他答案看起来都非常复杂;它的美在于它的简单性,FSM 可以非常复杂,有很多很多的状态和转换,但它们允许复杂的过程很容易分解和消化。

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

    【讨论】:

    • 完全同意。带有 switch 语句的简单 while 循环非常简单。
    • 除非您有一个非常复杂的状态机,其中包含许多状态和条件,否则您最终会得到多个嵌套开关。此外,忙等待可能会受到惩罚,具体取决于您的循环实现。
    【解决方案12】:

    FiniteStateMachine 是一个简单的状态机,用 C# 编写 Link

    使用我的库 FiniteStateMachine 的优势:

    1. 定义一个“上下文”类以向外部世界呈现单一界面。
    2. 定义一个 State 抽象基类。
    3. 将状态机的不同“状态”表示为 State 基类的派生类。
    4. 在适当的 State 派生类中定义特定于状态的行为。
    5. 在“上下文”类中维护指向当前“状态”的指针。
    6. 要更改状态机的状态,请更改当前的“状态”指针。

    下载DLLDownload

    LINQPad 上的示例:

    void Main()
    {
                var machine = new SFM.Machine(new StatePaused());
                var output = machine.Command("Input_Start", Command.Start);
                Console.WriteLine(Command.Start.ToString() + "->  State: " + machine.Current);
                Console.WriteLine(output);
    
                output = machine.Command("Input_Pause", Command.Pause);
                Console.WriteLine(Command.Pause.ToString() + "->  State: " + machine.Current);
                Console.WriteLine(output);
                Console.WriteLine("-------------------------------------------------");
    }
        public enum Command
        {
            Start,
            Pause,
        }
    
        public class StateActive : SFM.State
        {
    
            public override void Handle(SFM.IContext context)
    
            {
                //Gestione parametri
                var input = (String)context.Input;
                context.Output = input;
    
                //Gestione Navigazione
                if ((Command)context.Command == Command.Pause) context.Next = new StatePaused();
                if ((Command)context.Command == Command.Start) context.Next = this;
    
            }
        }
    
    
    public class StatePaused : SFM.State
    {
    
         public override void Handle(SFM.IContext context)
    
         {
    
             //Gestione parametri
             var input = (String)context.Input;
             context.Output = input;
    
             //Gestione Navigazione
             if ((Command)context.Command == Command.Start) context.Next = new  StateActive();
             if ((Command)context.Command == Command.Pause) context.Next = this;
    
    
         }
    
     }
    

    【讨论】:

    • 它有 GNU GPL 许可证。
    【解决方案13】:

    我会推荐state.cs。我个人使用过 state.js(JavaScript 版本)并且对它非常满意。该 C# 版本以类似的方式工作。

    你实例化状态:

            // create the state machine
            var player = new StateMachine<State>( "player" );
    
            // create some states
            var initial = player.CreatePseudoState( "initial", PseudoStateKind.Initial );
            var operational = player.CreateCompositeState( "operational" );
            ...
    

    你实例化了一些转换:

            var t0 = player.CreateTransition( initial, operational );
            player.CreateTransition( history, stopped );
            player.CreateTransition<String>( stopped, running, ( state, command ) => command.Equals( "play" ) );
            player.CreateTransition<String>( active, stopped, ( state, command ) => command.Equals( "stop" ) );
    

    您定义状态和转换的操作:

        t0.Effect += DisengageHead;
        t0.Effect += StopMotor;
    

    这就是(几乎)它。查看网站了解更多信息。

    【讨论】:

      【解决方案14】:

      在网上找到了这个很棒的教程,它帮助我了解了有限状态机。

      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();
          }
      
          (...)
      }
      

      【讨论】:

      • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
      • @drneel 我可以从教程中复制和粘贴部分内容……但这不会剥夺作者的功劳吗?
      • @JetBlue:留下答案中的链接作为参考,并在答案帖子中用您自己的话包含相关位,以免侵犯任何人的版权。我知道这看起来很严格,但由于这条规则,许多答案变得非常非常好。
      【解决方案15】:

      在我看来,状态机不仅用于更改状态,而且(非常重要)用于处理特定状态内的触发器/事件。如果您想更好地理解状态机设计模式,可以在书 Head First Design Patterns, page 320 中找到很好的描述。

      这不仅与变量中的状态有关,还与处理不同状态中的触发器有关。很棒的一章(不,我提到这一点是免费的 :-),其中只包含一个易于理解的解释。

      【讨论】:

        【解决方案16】:

        NuGet 中有 2 个流行的状态机包。

        Appccelerate.StateMachine(13.6K 下载 + 3.82K 旧版本 (bbv.Common.StateMachine))

        StateMachineToolkit(1.56K 下载)

        Appccelerate 库有 good documentation,但它不支持 .NET 4,所以我为我的项目选择了 StateMachineToolkit。

        【讨论】:

          【解决方案17】:

          我用 Juliet 的代码制作了这个通用状态机。它对我来说很棒。

          这些是好处:

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

          代码:

          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;
              }
          }
          

          这是 TryGetNext 方法的返回类型:

          public struct TransitionResult<TState>
          {
              public TransitionResult(TState newState, bool isValid)
              {
                  NewState = newState;
                  IsValid = isValid;
              }
              public TState NewState;
              public bool IsValid;
          }
          

          使用方法:

          这是从泛型类创建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);
              }
          }
          

          使用派生状态机

              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);
              }
          

          【讨论】:

            【解决方案18】:

            此回购中的其他替代方案https://github.com/lingkodsoft/StateBliss 使用流畅的语法,支持触发器。

                public class BasicTests
                {
                    [Fact]
                    public void Tests()
                    {
                        // Arrange
                        StateMachineManager.Register(new [] { typeof(BasicTests).Assembly }); //Register at bootstrap of your application, i.e. Startup
                        var currentState = AuthenticationState.Unauthenticated;
                        var nextState = AuthenticationState.Authenticated;
                        var data = new Dictionary<string, object>();
            
                        // Act
                        var changeInfo = StateMachineManager.Trigger(currentState, nextState, data);
            
                        // Assert
                        Assert.True(changeInfo.StateChangedSucceeded);
                        Assert.Equal("ChangingHandler1", changeInfo.Data["key1"]);
                        Assert.Equal("ChangingHandler2", changeInfo.Data["key2"]);
                    }
            
                    //this class gets regitered automatically by calling StateMachineManager.Register
                    public class AuthenticationStateDefinition : StateDefinition<AuthenticationState>
                    {
                        public override void Define(IStateFromBuilder<AuthenticationState> builder)
                        {
                            builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
                                .Changing(this, a => a.ChangingHandler1)
                                .Changed(this, a => a.ChangedHandler1);
            
                            builder.OnEntering(AuthenticationState.Authenticated, this, a => a.OnEnteringHandler1);
                            builder.OnEntered(AuthenticationState.Authenticated, this, a => a.OnEnteredHandler1);
            
                            builder.OnExiting(AuthenticationState.Unauthenticated, this, a => a.OnExitingHandler1);
                            builder.OnExited(AuthenticationState.Authenticated, this, a => a.OnExitedHandler1);
            
                            builder.OnEditing(AuthenticationState.Authenticated, this, a => a.OnEditingHandler1);
                            builder.OnEdited(AuthenticationState.Authenticated, this, a => a.OnEditedHandler1);
            
                            builder.ThrowExceptionWhenDiscontinued = true;
                        }
            
                        private void ChangingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
                        {
                            var data = changeinfo.DataAs<Dictionary<string, object>>();
                            data["key1"] = "ChangingHandler1";
                        }
            
                        private void OnEnteringHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
                        {
                            // changeinfo.Continue = false; //this will prevent changing the state
                        }
            
                        private void OnEditedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
                        {                
                        }
            
                        private void OnExitedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
                        {                
                        }
            
                        private void OnEnteredHandler1(StateChangeInfo<AuthenticationState> changeinfo)
                        {                
                        }
            
                        private void OnEditingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
                        {
                        }
            
                        private void OnExitingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
                        {
                        }
            
                        private void ChangedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
                        {
                        }
                    }
            
                    public class AnotherAuthenticationStateDefinition : StateDefinition<AuthenticationState>
                    {
                        public override void Define(IStateFromBuilder<AuthenticationState> builder)
                        {
                            builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
                                .Changing(this, a => a.ChangingHandler2);
            
                        }
            
                        private void ChangingHandler2(StateChangeGuardInfo<AuthenticationState> changeinfo)
                        {
                            var data = changeinfo.DataAs<Dictionary<string, object>>();
                            data["key2"] = "ChangingHandler2";
                        }
                    }
                }
            
                public enum AuthenticationState
                {
                    Unauthenticated,
                    Authenticated
                }
            }
            
            

            【讨论】:

              【解决方案19】:

              您可以使用我的解决方案,这是最方便的方法。它也是免费的。

              通过三个步骤创建状态机:

              1.node editor? 中创建方案并使用library? 将其加载到您的项目中

              StateMachine stateMachine = new StateMachine("scheme.xml");
              

              2.描述你的应用逻辑事件⚡

              stateMachine.GetState("State1").OnExit(Action1);
              stateMachine.GetState("State2").OnEntry(Action2);
              stateMachine.GetTransition("Transition1").OnInvoke(Action3);
              stateMachine.OnChangeState(Action4);
              

              3.运行状态机?

              stateMachine.Start();
              

              链接:

              节点编辑器:https://github.com/SimpleStateMachine/SimpleStateMachineNodeEditor

              图书馆:https://github.com/SimpleStateMachine/SimpleStateMachineLibrary

              【讨论】:

                【解决方案20】:

                不确定我是否错过了重点,但我认为这里的答案都不是“简单”的状态机。我通常所说的简单状态机是使用内部带有开关的循环。这就是我们在 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;
                        }
                    }
                }
                

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

                【讨论】:

                  【解决方案21】:

                  列表的另一个状态机,我的:https://github.com/IanMercer/Abodit.StateMachine

                  除了具有进入和退出动作的简单状态以及每次转换的动作之外,这一状态还专为在异步代码中使用而设计。它还支持分层状态和复合状态机。所以不是真的“简单”,但在使用中很容易对状态和转换进行编码。

                  static OpenClosedStateMachine()
                  {
                      Closed
                         .When(Fridge.eDoorOpens, (m, s, e, c) => Task.FromResult(Open));
                  
                      Open
                          .When(Fridge.eDoorCloses, (m, s, e, c) => Task.FromResult(Closed));
                  }
                  

                  与其他人不同,它还支持时间转换,因此很容易转换到不同的状态 After 给定时间段或 At 给定时间。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2011-04-24
                    • 2010-11-25
                    • 2010-10-06
                    • 2019-12-30
                    • 2010-12-13
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多