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

更新:

再次感谢你的例子,它们非常有帮助,下面这些,我不是说

不是目前给出的例子,据我所知&状态机,只有我们通常理解的状态机的一半?< br / > 在这种意义上,示例确实改变了状态,但这仅通过改变变量的值来表示(并允许不同的值-在不同的状态下发生变化),而通常情况下,状态机也应该改变其行为,并且行为不(仅)允许根据状态对变量进行不同的值更改,而是允许对不同的状态执行不同的方法

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


最初的问题:

我发现这个关于状态机;c#中的迭代器块和工具来创建状态机和c#的讨论,所以我发现了很多抽象的东西,但作为一个新手,所有这些都有点令人困惑。

因此,如果有人能提供一个c#源代码-示例,实现一个简单的状态机,可能只有3,4个状态,那就太好了,只是为了了解它的要点。

313462 次浏览

您可以编写一个迭代器块,使您能够以编排的方式执行代码块。代码块是如何分解的并不一定要对应于任何东西,这只是你想要如何编码它。例如:

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。正如您所看到的,这是一种编排事情应该如何发生的有用机制。

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

简单状态机图

我们有:

  • 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函数来返回下一个状态确定性,用MoveNext函数来改变状态机。

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

例如,你可以用函数实现一个状态机:

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

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

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

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

下面是一个非常经典的有限状态机的例子,它模拟了一个非常简化的电子设备(比如电视)

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

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

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

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


fsm.Fire(Command.Begin);

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

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

我最初的答案是经典的命令式代码。我认为它在代码运行时非常直观,因为数组使得状态机的可视化变得简单。缺点是你必须把这些都写下来。雷莫的答案减轻了编写样板代码的工作量,但远没有那么直观。还有第三种选择;画状态机。

如果你正在使用。net并且可以将运行时版本4作为目标,那么你可以选择使用工作流的状态机活动。它们本质上让你绘制状态机(很像朱丽叶的图),并让WF运行时为你执行它。

详细信息请参见MSDN文章使用Windows Workflow Foundation构建状态机,最新版本请参见这个CodePlex网站

这是我在瞄准。net时更喜欢的选项,因为它很容易看到、更改和向非程序员解释;俗话说,图片胜过千言万语!

这里有些无耻的自我宣传,但在不久前,我创建了一个名为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成员并向前移动状态机。

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

关于stateppattern。这符合你的需要吗?

我认为这与上下文有关,但值得一试。

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

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

布鲁诺

我刚刚贡献了这个:

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

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

所描述的那样

enter image description here

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

实现和使用at: 实现。net ThreadState by State设计模式

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

我相信我所知道的方法被称为“迭代循环”。在其中,你实际上有一个“while”循环,它周期性地根据事件(中断)退出,然后再次返回到主循环。

在中断处理程序中,您将传递一个CurrentState并返回一个NextState,然后在主循环中覆盖CurrentState变量。你可以无限地这样做,直到程序关闭(或微控制器复位)。

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

我知道我的回答不应该包含另一个问题,但我不得不问:为什么这些其他提出的解决方案看起来如此复杂?< br > 它们似乎类似于用一个巨大的大锤敲一个小钉子

finitestatemmachine是一个简单的状态机,用c# 链接编写

使用我的库finitestatemmachine的优点:

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

下载DLL 下载

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;




}


}

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

差不多就是这样了。更多信息请查看网站。

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

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

本教程是语言无关的,所以它可以很容易地适应您的c#需求。

而且,所使用的例子(一只蚂蚁寻找食物)很容易理解。

< p > < br > 来自教程:

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


(...)
}

在我看来,状态机不仅意味着改变状态,而且(非常重要)还意味着处理特定状态中的触发器/事件。如果你想更好地理解状态机设计模式,可以在头部优先设计模式,320页这本书中找到一个很好的描述。

它不仅涉及变量中的状态,还涉及处理不同状态中的触发器。很棒的一章(不,提到这个对我来说是免费的:-),它包含了一个简单易懂的解释。

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

Appccelerate。StateMachine (13.6K下载+ 3.82K遗留版本(bbv.Common.StateMachine))

StateMachineToolkit (1.56K下载)

Appccelerate库有好的文档,但它不支持。net 4,所以我选择StateMachineToolkit为我的项目。

我用Juliet的代码做了这个通用状态机。这对我来说非常有效。

以下是好处:

  • 你可以用两个枚举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的方法:

为其状态定义一个enum OnlineDiscountState,为其命令定义一个enum 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);
}

其他替代在这个repo 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
}
}


你可以用我的方法,这是最方便的方法。它也是免费的。

三个步骤中创建状态机:

1.节点编辑🔗中创建方案,并使用图书馆📚将其加载到项目中

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

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

如果它真的应该是一个状态机,你调用的方法会根据你所处的状态做出不同的反应:状态设计模式是更好的方法

列表的另一个状态机是我的: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

当我使用像RabbitMQ或Rabbit这样的消息代理时,我也很难使用状态机。

我制作这个视频是为了帮助别人。

https://www.youtube.com/watch?v=Vwfngk0YhLs&t=11s&ab_channel=GarryTaylor

代码来自这个Github回购

https://github.com/welhell/masstransit-saga-example