更新:
再次感谢你的例子,它们对我很有帮助,我并不是说
夺走他们的一切。
Aren't the currently given examples, as far as I understand them & state-machines, only half of what we usually understand by a state-machine?
In the sense that the examples do change state but that's only represented by changing the value of a variable (and allowing different value- changes in different states), while usually, a state machine should also change its behavior, and behavior not (only) in the sense of allowing different value changes for a variable depending on the state, but in the sense of allowing different methods to be executed for different states.
还是我对状态机及其常用用法有误解?
最初的问题:
我发现了关于c#中的状态机和迭代器块的讨论,以及用于创建状态机和c#的工具,所以我发现了很多抽象的东西,但作为一个新手,所有这些都有点令人困惑。
因此,如果有人能提供一个c#源代码-示例,实现一个简单的状态机,可能只有3,4个状态,那就太好了,只是为了了解它的要点。
您可能希望使用现有的开源有限状态机之一。例如,在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
其他替代在此回购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
}
}
您可以编写一个迭代器块,使您能够以编排的方式执行代码块。代码块是如何分解的并不一定要对应于任何东西,这只是你想要如何编码它。例如:
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。正如您所看到的,这是一种编排事情应该如何发生的有用机制。
您可能希望使用现有的开源有限状态机之一。例如,在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
这里有些无耻的自我宣传,但在不久前,我创建了一个名为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成员并向前移动状态机。
但是你不需要真正理解它的内部原理就能使用它。