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

时间:2011-05-07 20:22:39

标签: c# state-machine

更新:

再次感谢这些例子,他们非常乐于助人,以下我不是故意的 从他们那里拿走任何东西。

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

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

祝你好运


原始问题:

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

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


22 个答案:

答案 0 :(得分:388)

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

simple state machine diagram

我们有:

  • 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 :(得分:70)

您可能希望使用现有的开源有限状态机之一。例如。 bbv.Common.StateMachine在http://code.google.com/p/bbvcommon/wiki/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);

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

答案 2 :(得分: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; }
    }
}
}

答案 3 :(得分:20)

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

state machine of a lamp

请注意,此状态机有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

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

    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的方法在内部创建了一个状态机。这个构造通常用于懒惰地创建数据序列,但在这种情况下,我们实际上并不对返回的序列感兴趣(无论如何都是null),而是在引擎盖下创建的状态行为。

StateMachine基类对构造做了一些反思,以便为每个[Trigger]操作分配代码,该操作设置Trigger成员并向前移动状态机。

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

答案 4 :(得分:10)

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

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。正如你所看到的,它是一种有用的机制,可以协调事情的发生。

答案 5 :(得分:8)

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

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

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

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

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

答案 6 :(得分: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);
}

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

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

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

答案 7 :(得分:6)

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

enter image description here

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

Implements .NET ThreadState by State Design Pattern

执行和使用

答案 8 :(得分:5)

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

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

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

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

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

答案 9 :(得分:5)

在线发现了这个很棒的教程,它帮助我了解有限状态机。

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

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

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


来自教程:

enter image description here

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

    (...)
}

答案 10 :(得分:3)

我刚刚贡献了这个:

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

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

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

答案 11 :(得分:3)

多么好的StatePattern。这符合您的需求吗?

我认为它的背景相关,但值得一试。

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

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

布鲁诺

答案 12 :(得分:2)

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

不仅关于变量内的状态,还关于处理不同状态内的触发器。伟大的章节(不,我提到这个:-)没有任何费用,其中只包含一个易于理解的解释。

答案 13 :(得分:2)

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

这些是好处:

  • 您可以在代码中使用两个枚举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;
    }
}

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

答案 14 :(得分:0)

我认为Juliet提出的状态机有一个错误:方法 GetHashCode 可以为两个不同的转换返回相同的哈希码,例如:

  

State = Active(1),Command = Pause(2)=&gt; HashCode = 17 + 31 + 62 =   110

     

State = Paused(2),Command = End(1)=&gt; HashCode = 17 + 62 + 31 =   110

为避免此错误,该方法应如下所示:

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

亚历

答案 15 :(得分:0)

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

编写

优点tu使用我的库FiniteStateMachine:

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

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

答案 16 :(得分:0)

我会推荐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;

那就是(差不多)它。查看网站了解更多信息。

答案 17 :(得分:0)

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

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

StateMachineToolkit(1.56K下载)

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

答案 18 :(得分:0)

此存储库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 :(得分:0)

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

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

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 :(得分:0)

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

enter image description here

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

答案 21 :(得分:0)

列表的另一个状态机,我的: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 给定时间转换到不同状态。