更新:
再次感谢这些例子,他们非常乐于助人,以下我不是故意的 从他们那里拿走任何东西。
根据我的理解,它们不是当前给出的例子。状态机,只是我们通常通过状态机理解的一半? 在某种意义上,示例确实改变了状态,但这只是通过改变变量的值来表示(并允许在不同的状态中允许不同的值变化),而通常状态机也应该改变它的行为,并且行为不是(仅)在根据状态允许对变量进行不同值更改的意义,但是允许为不同状态执行不同的方法。
或者我对状态机及其常见用途存在误解?
祝你好运
原始问题:
我发现了关于state machines & iterator blocks in c#的讨论和创建状态机的工具以及C#的不适用的东西,所以我发现了很多抽象的东西,但是作为一个菜鸟,所有这些都有点令人困惑。
如果有人能够提供一个C#源代码示例,它可以实现一个简单的状态机,可能有3,4个状态,那就太好了。只是为了得到它的要点。
答案 0 :(得分:388)
让我们从这个简单的状态图开始:
我们有:
您可以通过多种方式将其转换为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的库,它允许以非常简洁的方式描述有限复杂度的状态机。例如,考虑一盏灯:
请注意,此状态机有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#的图片所示
您可以轻松添加新状态,将状态从一种状态配置到另一种状态非常容易,因为它已在状态实现中封装
执行和使用答案 8 :(得分:5)
我还没有尝试在C#中实现FSM,但这些听起来(或看起来)非常复杂,因为我过去在C或ASM等低级语言中处理FSM的方式。
我相信我一直都知道的方法叫做“迭代循环”。在它中,你基本上有一个'while'循环,它根据事件(中断)定期退出,然后再次返回主循环。
在中断处理程序中,您将传递一个CurrentState并返回一个NextState,然后在主循环中覆盖CurrentState变量。在程序关闭(或微控制器重置)之前,您无限制地执行此操作。
我认为其他答案看起来都非常复杂,而在我看来,FSM是如何实现的;它的美丽在于它的简洁性和FSM可能非常复杂,有许多很多状态和转换,但它们允许复杂的过程容易被分解和消化。
我意识到我的回答不应该包括另一个问题,但我不得不问:为什么这些其他提议的解决方案看起来如此复杂?
它们似乎类似于用巨大的大锤击打小钉子。
答案 9 :(得分:5)
在线发现了这个很棒的教程,它帮助我了解有限状态机。
本教程与语言无关,因此可以轻松地适应您的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();
}
(...)
}
答案 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)
我用朱丽叶的代码制作了这个通用状态机。它对我来说很棒。
这些是好处:
TState
和TCommand
创建新的状态机,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:
下载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;
}
}
}
如果它确实是一个状态机,您可以在其上调用根据您所处的状态不同做出反应的方法:状态设计模式是更好的方法
答案 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
给定时间转换到不同状态。