Qual é a melhor (quando o desempenho é importante) maneira de implementar uma máquina de estado em C #?
-
06-07-2019 - |
Pergunta
Eu vim com as seguintes opções:
Usando a instrução goto:
Start:
goto Data
Data:
goto Finish
Finish:
;
usando a instrução switch:
switch(m_state) {
case State.Start:
m_state = State.Data;
break;
case State.Data:
m_state = State.Finish;
break;
case State.Finish:
break;
}
usando Goto e alternar entre si:
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;
}
Eu prefiro a primeira opção utilizando a instrução goto, porque é mais rápido e menos detalhado. Mas eu não tenho certeza se é a melhor opção. Em termos de desempenho, talvez, mas quando se trata de legibilidade eu não sei. É por isso que faço esta pergunta. Qual opção você prefere e por quê?
Solução
A vantagem com o interruptor sobre o Goto é que você tem estado em uma variável, não apenas no ponteiro de instrução.
Com o método goto máquina estatal tem que ser o loop principal que controla tudo o resto, porque você não pode sair dele porque você perderia o estado.
Com o método chave da máquina estatal é isolado, e você pode ir a qualquer lugar que você deseja manipular eventos a partir do exterior. Quando você voltar para a máquina de estado, ele só continua onde yuu parou. Você pode até ter mais do que uma máquina de estado correndo lado a lado, algo que não é possível com a versão Goto.
Eu não tenho certeza onde você está indo com a terceira alternativa, olha só como a primeira alternativa com um interruptor inútil em torno dele.
Outras dicas
Eu prefiro mutuamente chamando / funções recursivas. Para adaptar o seu exemplo:
returnvalue Start() {
return Data();
}
returnvalue Data() {
return Finish();
}
returnvalue Finish() {
…
}
Teoricamente, este pode ser completamente sequenciados de modo que a saída do compilador é equivalente à sua solução goto
(daí, mesma velocidade). Realisticamente, o compilador C # / Jitter provavelmente não vai fazê-lo . Mas desde que a solução é muito mais legível (bem, IMHO), eu só iria substituí-lo com a solução goto
depois de uma prova de referência muito cuidado para que ele é , de fato inferior em termos de velocidade, ou que estouros de pilha ocorrer (não nesta solução simples mas maior autômatos executar para esse problema).
Mesmo assim, eu o faria definitivamente vara para a solução goto case
. Por quê? Porque, então, todo o seu bagunçado goto
pasta está bem encaixada dentro de uma estrutura de bloco (o bloco switch
) e seus spaghetti não vai mangle o resto do código, evitando Bolognese.
Em conclusão : a variante funcional é clara, mas em geral propensos a problemas. A solução goto
é confuso. Somente goto case
oferece uma solução a meio caminho limpo, eficiente. Se o desempenho é realmente fundamental (e o autômato é o gargalo de garrafa), vá para a variante goto case
estruturado.
Há uma quarta opção.
Use um iterador para implementar um StateMachine. Aqui é um agradável curta artigo mostrando como
Ele tem algumas desvantagens embora. Manipulando o estado de fora do iterador não é possível.
Eu também não tenho certeza se ele é muito rápido. Mas você sempre pode fazer um teste.
Se você quiser quebrar a lógica de transição máquina de estado em funções separadas, você só pode fazê-lo usando declarações switch.
switch(m_state) {
case State.Start:
m_state = State.Data;
break;
case State.Data:
m_state = ComputeNextState();
break;
case State.Finish:
break;
}
Também é mais legível, ea sobrecarga da instrução switch (versus Goto) só vai fazer uma diferença de desempenho em raras circunstâncias.
EDIT:
Você pode usar "goto case" para fazer pequena melhoria de desempenho:
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;
}
No entanto, você corre o risco de esquecer de atualizar a variável de estado. Que pode causar erros sutis, mais tarde, (porque você assumiu que "m_state" foi definido), então eu sugiro evitá-lo.
Pessoalmente eu prefiro segundo com Goto desde primeiro exigirá passo laço desnecessários (por exemplo) para ir para o novo estado