基于 C#/XML 的状态引擎





4.00/5 (7投票s)
2004年7月21日
3分钟阅读

48570

645
如何创建一个基于 XML 的状态引擎来控制一个简单的实时系统
引言
在被要求研究如何更新旧的 C++ 软件时,我需要在 C# 中实现一个简单的状态引擎。 想要创建一个可以在整个代码中使用的通用解决方案,我研究了将实际的状态表写在 XML 中,以及一个使用反射处理该表的通用引擎。
Using the Code
XML 中的状态表仅定义了许多状态,并通过数字进行标识。 每个状态都有一个函数名称和一个相关的操作,该操作在函数调用完成时执行。
<?xml version="1.0" encoding="utf-8" ?>
<StateTable>
<State Number="0">
<Function>CheckInputs</Function>
<Action Type="SLEEP">
<SleepTime>1000</SleepTime>
</Action>
</State>
<State Number="10">
<Function>FirstPressed</Function>
<Action Type="MOVETONEXT">
<SleepTime>100</SleepTime>
<NextState>0</NextState>
</Action>
</State>
<State Number="20">
<Function>SecondPressed</Function>
<Action Type="MOVETONEXT">
<SleepTime>300</SleepTime>
<NextState>0</NextState>
</Action>
</State>
<State Number="30">
<Function>BothPressed</Function>
<Action Type="EXIT" />
</State>
<State Number="40">
<Function>NonePressed</Function>
<Action Type="MOVETONEXT">
<SleepTime>1000</SleepTime>
<NextState>0</NextState>
</Action>
</State>
</StateTable>
状态引擎通过使用反射来定位函数,并将其附加到状态的事件。 这是通过首先获取状态的事件类型,然后通过指定目标类 (this.engineMethodsClass
) 实例和实例方法的名称 (functionName
) 将委托附加到它来实现的。
// get our OnState event, then create and add a new delegate to the event
System.Reflection.EventInfo eventInfo = state.GetType().GetEvent("OnState");
System.Delegate eventDelegate = System.Delegate.CreateDelegate(
eventInfo.EventHandlerType, this.engineMethodsClass, functionName);
eventInfo.AddEventHandler(state, eventDelegate);
一个操作由以下枚举值组成
SLEEP
- 导致引擎停留在当前状态并休眠一段时间(以毫秒为单位)MOVETONEXT
- 移至下一个状态(由<NextState>
标签指示)。 也可以在这里添加睡眠时间。EXIT
- 导致引擎退出
要使用状态引擎,首先在处理状态引擎的类中创建一个变量。
private RQAssemblies.StateEngine.Engine engine = new
RQAssemblies.StateEngine.Engine();
为了将事件与主应用程序分开,您可以指定可以找到事件的类。 在本例中,我们将简单地指向我们的主应用程序。 然后,我们可以将 state
表加载到引擎中,并指示它 Run()
。
// initialise and start up the engine
engine.EngineMethodsClass = this;
engine.LoadStateTable(@"..\..\BasicEngine.xml");
engine.Run();
引擎在其自己的线程上运行以进行执行,因此必须在应用程序终止时停止。 将以下代码放在应用程序的 Close
事件中。
// instruct the state engine to stop execution
engine.Stop();
当引擎到达一个 state
时,它会调用附加的事件。 在我们的示例 state
表中,我们需要定义 5 个事件,其中事件的原型是 public void functionName(Action actionOnExit)
。
public void CheckInputs(RQAssemblies.StateEngine.Action actionOnExit)
{
if((this.input1.Checked == true) && (this.input2.Checked == false))
{
actionOnExit.Type =
RQAssemblies.StateEngine.Action.ActionType.MOVETONEXT;
actionOnExit.StateNumberToMoveTo = 10;
}
else if((this.input1.Checked == false) && (this.input2.Checked == true))
{
actionOnExit.Type =
RQAssemblies.StateEngine.Action.ActionType.MOVETONEXT;
actionOnExit.StateNumberToMoveTo = 20;
}
else if((this.input1.Checked == true) && (this.input2.Checked == true))
{
actionOnExit.Type =
RQAssemblies.StateEngine.Action.ActionType.MOVETONEXT;
actionOnExit.StateNumberToMoveTo = 30;
}
else if((this.input1.Checked == false) && (this.input2.Checked == false))
{
actionOnExit.Type =
RQAssemblies.StateEngine.Action.ActionType.MOVETONEXT;
actionOnExit.StateNumberToMoveTo = 40;
}
}
public void FirstPressed(RQAssemblies.StateEngine.Action actionOnExit)
{
this.result.Text = "First Pressed";
}
public void SecondPressed(RQAssemblies.StateEngine.Action actionOnExit)
{
this.result.Text = "Second Pressed";
}
public void BothPressed(RQAssemblies.StateEngine.Action actionOnExit)
{
this.result.Text = "Both Pressed";
}
public void NonePressed(RQAssemblies.StateEngine.Action actionOnExit)
{
this.result.Text = "None Pressed";
}
在一个事件中,我们可以通过修改 actionOnExit
参数来修改状态的退出操作。
在我们的示例状态表中,状态 0
只是循环并检查 2 个按钮的状态。 根据按钮状态,我们修改退出操作条件,以便分支到 state
表的不同部分。
关注点
当我第一次开始学习 C# 时,我将反射视为一个从 C++ 带来的新鲜空气,但很难找到一个真实世界/与工作相关的应用程序来尝试。 现在我找到了一个,我可以看到它为我打开的门,以便在将来显着减少我的编码工作。
结论
状态引擎的实现非常基础,它只是为了展示如何在创建实时状态引擎时利用 XML 和反射的优势。 显然,这个例子并没有与一个实时系统交互,因为我无法将我的条形码扫描器与这篇文章一起压缩。 在未来,我打算修改此代码,使其在状态之间的链接方面更加灵活,并提供外部输入状态,以及可能提供“是/否”风格的状态处理。
但与此同时,我希望您在这篇文章中找到一些有用的东西,并且我期待着阅读您的评论。
历史
- 版本 1.0 - 20日 2004年7月 - 原始版本
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。