65.9K
CodeProject 正在变化。 阅读更多。
Home

基于层次结构 XML 的 C# 状态机生成器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.15/5 (6投票s)

2011年2月19日

CPOL

3分钟阅读

viewsIcon

30837

downloadIcon

471

在 XML 中设计一个状态机,并生成代表它的 C# 代码。

引言

本文讨论如何使用基于 XML 和 C# 代码隐藏创建分层状态机。

目的

状态机允许我们以具体且可管理的形式表示一个问题,通常具有一个通用的解决方案。状态机代码允许使用 XML 编写基于层次结构的状态机,并生成相应的 C# 代码文件,该文件表示具有执行状态机所需的所有语义的状态机。

创建状态机

使用状态机的大部分代码都只是用 XML 编写

<StateMachines name="<NamespaceName>" xmlns="StateMachines">
    <StateMachine name="<ClassName>">
        <States>
            <State name="<StateName>"/>
        </States>
        <Events>
            <Event name="<EventName>"/>
        </Events>
        <Transitions>
            <Transition from="<StateName>"
                to="<StateName>" trigger="<EventName>"/>
        </Transitions>
    </StateMachine>
</StateMachines>

一个 State 元素可以是另一个 StateMachine,格式完全相同。正确的 XML 格式通过模式提供,但不会检查有效引用。以下 C# 代码是从 XML 生成的

using StateMachines;

namespace StateMachines.NamespaceName
{
    public partial class ClassName :
    StateMachine<ClassName.States, ClassName.Events, ClassName.Transitions>
    {
        public enum States
        {
            StateName
        }

        public enum Events
        {
            EventName
        }

        public enum Transitions
        {
            EventName__StateName_StateName
        }

        public ClassName()
        {
            _States[(int)States.StateName] =
                (this.New(this, States.StateName));

            InitialState = _States[(int)States.StateName];
            _currentState = InitialState;

            _Transitions
            [(int)Transitions.EventName__StateName_StateName] =
            new Transition<States, Events, Transitions>
            (States.StateName, States.StateName, Events.EventName);
            _Transitions[(int)
            Transitions.EventName__StateName_StateName].Conditions =
            new Condition(() => { return true; });
        }

        public new void Fire(ClassName.Events Event, params object[] args)
        {
            switch(Event)
            {
                case Events.EventName:
                if (_Transitions[(int)Transitions.EventName__
                    StateName_StateName].Eval())
                {
                    CurrentState =
                        _States[(int)States.StateName];
                }
                break;
            }
        }

        // User defined functions. Must Implement.
        // Comment out or remove these declarations when used.
    }
}

要使用该代码,您只需实例化状态机,向其事件添加处理程序,然后触发它们。要生成状态机,您必须将文件添加到 StateMachine.tt 解析器。

状态

状态是表示状态机中状态的对象。它们只能由 StateMachine 实例化,并且权重较低。状态机本身就是一个状态。

事件

所有状态都存在标准的 OnEnterOnLeave 事件。

条件语句

每个转换都可以有一组可选的条件,必须通过这些条件才能进行转换。这是为了降低设置复杂条件的复杂性。一个转换可能有一个分层的条件块,该条件块将在转换发生之前被评估。

<Transitions>
    <Transition from="<StateName>" to="<StateName>" trigger="<EventName>">
        <Conditions op="<OperationType>">
            <Condition name="<ConditionName" op="<OperationType>">
        </Conditions>
    </Transition>
</Transitions>

<Conditions> 添加一个条件块,并且可能是嵌套的。 <Condition> 通过 "ConditionName" 引用类中的方法,而 op 是组合条件的操作。

添加功能

在示例代码中,有一个鼠标与对象交互的状态机示例。该机器的基本代码是

void B_MouseEnter(object sender, MouseEventArgs e)
{
    switch (mouseSM.CurrentState._State)
    {
        case MouseSM.States.Waiting :
            mouseSM.Waiting.Fire(Waiting.Events.IntoObject, sender);
            mouseSM.Waiting.ForceCurrentState(Waiting.States.OnObject);
            mouseSM.LeftButton.ForceCurrentState
                    (LeftButton.States.OnObject);
            mouseSM.RightButton.ForceCurrentState
                    (RightButton.States.OnObject);
            break;

        case MouseSM.States.LeftButton :
            mouseSM.LeftButton.Fire
                (LeftButton.Events.IntoObject, sender);
            mouseSM.Waiting.ForceCurrentState(Waiting.States.OnObject);
            mouseSM.LeftButton.ForceCurrentState
                (LeftButton.States.OnObject);
            mouseSM.RightButton.ForceCurrentState
                (RightButton.States.OnObject);
            break;

        case MouseSM.States.RightButton :
            mouseSM.RightButton.Fire
                (RightButton.Events.IntoObject, sender);
            mouseSM.Waiting.ForceCurrentState(Waiting.States.OnObject);
            mouseSM.LeftButton.ForceCurrentState
                (LeftButton.States.OnObject);
            mouseSM.RightButton.ForceCurrentState
                (RightButton.States.OnObject);
            break;

        default: break;
    }

    CurrentState.Text = mouseSM.ActiveState.ToString();
}

这里的处理程序只是在与对象(在本例中为 Button)交互时触发适当的事件。在这种情况下,ForceCurrentState 用于使所有顶级状态在它们位于按钮上方或不在按钮上方时相互跟随。这是为了让我们进入正确的子状态。可以通过使用一个通用的事件类并重写状态机来避免这种情况。在这种情况下,我们实际上有三个状态机同时工作。一个是在按钮上或不在按钮上,另一个是鼠标按钮按下或未按下。第三个状态机可以用来管理这两个状态机的可能组合结果。也可以将其实现为一个状态机,并具有诸如 LeftButtonOnObjectLeftButtonOffObject 之类的状态。

我们还在示例代码中挂钩了没有控制台输出的 OnEventOnLeave

结论

此代码的用处在于减少了实现状态机所需的所有样板代码以及 C# 的类型安全性。发布此代码的主要目的是为您的状态机代码项目提供基础。虽然使用了 T4,但它的设计方式使其不是必需的,并且代码生成可以轻松地迁移到它自己的可执行文件。可以添加一个可视化前端来生成状态机。

项目中需要的文件是

  • StateMachine.cs
  • StateMachineGen.tt
  • StateMachine.xsd
  • T4Utility.tt

待办事项

添加使用一组通用的事件、转换、状态和条件的能力。这可能会使状态机过于复杂,但在某些情况下可以显着降低复杂性。

历史

  • v0.5 - 2011 年 2 月 19 日 - 首次发布。
  • v0.55 - 2011 年 2 月 21 日 - 添加了本地状态字段以便于状态访问。更新了将数据传递给事件处理程序。
© . All rights reserved.