在C#中实现状态机的最佳方法(性能何时重要)?

时间:2009-10-24 12:31:53

标签: c# switch-statement goto state-machine

我提出了以下选项:

使用goto语句:

Start:
    goto Data
Data:
    goto Finish
Finish:
    ;

使用switch语句:

switch(m_state) {
    case State.Start:
        m_state = State.Data;
        break;
    case State.Data:            
        m_state = State.Finish;
        break;
    case State.Finish:
        break;
}

使用goto并一起切换:

switch(m_state) {
    case State.Start:
        goto case State.Data2;
    case State.Data1:
        goto case State.Finish;
    case State.Data2:
        m_state = State.Data1;
        //call to a function outside the state machine
        //that could possibly change the state
        break;
    case State.Finish:
        break;
}

我更喜欢使用goto语句的第一个选项,因为它更快,更简洁。但我不确定它是不是最好的选择。表现明智,但是当谈到可读性我不知道。这就是我问这个问题的原因。您更喜欢哪个选项?为什么?

5 个答案:

答案 0 :(得分:3)

我更喜欢相互调用/递归函数。为了适应你的榜样:

returnvalue Start() {
    return Data();
}

returnvalue Data() {
    return Finish();
}

returnvalue Finish() {
    …
}

理论上,此可以完全内联,以便编译器输出等同于goto解决方案(因此速度相同)。实际上,the C# compiler /JITter probably won’t do it。但是由于解决方案的可读性更高(嗯,恕我直言),我只会在经过非常仔细的基准测试后将其替换为goto解决方案,证明它 在速度方面确实较差,或者发生堆栈溢出(不是在这个简单的解决方案中,但是更大的自动机会遇到这个问题)。

即使这样,我也肯定坚持goto case解决方案。为什么?因为那时你的整个凌乱的goto意大利面被封装在一个块状结构(switch块)中,你的意大利面条不会破坏其余的代码,阻止了博洛尼亚。

总之:功能变体很明显,但通常容易出问题。 goto解决方案很麻烦。只有goto case提供了一个干净,高效的解决方案。如果性能确实是最重要的(并且自动机是瓶颈),那么请选择结构化的goto case变体。

答案 1 :(得分:2)

切换goto的优点是你在变量中有状态,而不仅仅是在指令指针中。

使用goto方法,状态机必须是控制其他所有内容的主循环,因为你不会因为失去状态而退出它。

使用switch方法,状态机是隔离的,您可以到任何想要处理外部事件的地方。当你返回状态机时,它会继续在yuu停止的地方继续。您甚至可以并排运行多个状态机,这是goto版本无法实现的。

我不确定你选择第三种替代方案,它看起来就像第一种替代方案,它周围有一个无用的开关。

答案 2 :(得分:2)

如果您想将状态机转换逻辑分解为单独的函数,则只能使用switch语句来执行此操作。

switch(m_state) {
        case State.Start:
                m_state = State.Data;
                break;
        case State.Data:                        
                m_state = ComputeNextState();
                break;
        case State.Finish:
                break;
} 

它也更具可读性,并且switch语句(与Goto相比)的开销只会在极少数情况下产生性能差异。

编辑:

您可以使用“转到大小写”来提高性能:

switch(m_state) {
        case State.Start:
                m_state = State.Data; // Don't forget this line!
                goto case State.Data;
        case State.Data:                        
                m_state = ComputeNextState();
                break;
        case State.Finish:
                break;
} 

但是你冒着忘记更新状态变量的风险。这可能会在以后导致细微的错误(因为你假设“m_state”被设置),所以我建议避免它。

答案 3 :(得分:2)

有第四种选择。

使用迭代器实现状态机。这是一个nice short article,向您展示如何

但它有一些缺点。无法从迭代器外部操纵状态。

我也不确定它是否很快。但你总能做一个测试。

答案 4 :(得分:0)

我个人更喜欢第二个使用goto,因为第一个需要不必要的循环步骤(例如)才能进入新状态