Was ist die beste (für seine Leistung) Art und Weise eine Zustandsmaschine in C # zu implementieren?

StackOverflow https://stackoverflow.com/questions/1617894

Frage

Ich kam mit den folgenden Optionen auf:

die goto-Anweisung:

Start:
    goto Data
Data:
    goto Finish
Finish:
    ;

mit der switch-Anweisung:

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

mit goto und Schalter zusammen:

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;
}

Ich ziehe es die erste Möglichkeit, die goto-Anweisung verwenden, weil es schneller und weniger ausführlich ist. Aber ich bin nicht sicher, ob es die beste Option ist. Performance weise vielleicht, aber wenn es um die Lesbarkeit kommt ich weiß es nicht. Deshalb habe ich diese Frage stellen. Welche Option bevorzugen Sie und warum?

War es hilfreich?

Lösung

Der Vorteil mit dem Schalter über die goto ist, dass Sie den Zustand in einer Variablen, nicht nur in den Befehlszeigern haben.

Mit dem goto Methode der Zustandsmaschine hat die Hauptschleife sein, die alles andere kontrolliert, weil man nicht aus ihm heraus treten kann, weil Sie den Zustand verlieren würde.

Mit dem Schalter Methode ist die Zustandsmaschine getrennt, und Sie können überall hin mitnehmen wollen von außen Ereignisse behandeln. Wenn Sie an die Zustandsmaschine zurückkehren, es einfach weiter, wo yuu aufhörte. Sie können sogar mehr als eine Zustandsmaschine Seite ausgeführt haben sie, etwas, das nicht mit der goto-Version ist.

Ich bin nicht sicher, wo man mit der dritten Alternative werden, es sieht genauso aus wie die erste Alternative mit einem nutzlosen Schalter um ihn herum.

Andere Tipps

Ich ziehe es für beide Seiten aufrufen / rekursive Funktionen. Zur Anpassung Ihr Beispiel:

returnvalue Start() {
    return Data();
}

returnvalue Data() {
    return Finish();
}

returnvalue Finish() {
    …
}

Theoretisch diese kann vollständig inlined werden, so dass der Compiler-Ausgang entspricht Ihre goto Lösung (also mit gleicher Geschwindigkeit). Realistisch betrachtet, der C # -Compiler / Jitters wird wahrscheinlich nicht tun . Aber da die Lösung erheblich besser lesbar ist (gut, IMHO), würde ich nur ersetzen, es mit der goto Lösung nach einer sehr sorgfältigen Benchmark beweist, dass es ist in der Tat schlechter in Bezug auf Geschwindigkeit, oder dass Stapelüberlauf auftreten (nicht in dieser einfachen Lösung, aber größere Automaten laufen in dieses Problem).

Selbst dann würde ich auf jeden Fall Stick auf die goto case Lösung. Warum? Denn dann ist deine ganze unordentlich goto Pasta in einer Blockstruktur (der switch Block) gut umhüllt und Ihre Spaghetti werden den Rest des Codes nicht mangle, verhindert Bolognese.

Fazit : die funktionelle Variante ist klar, aber im Allgemeinen anfällig für Probleme. Die goto Lösung ist chaotisch. Nur goto case bietet eine halbwegs saubere, effiziente Lösung. Wenn die Leistung in der Tat von größter Bedeutung ist (und der Automat ist der Flaschenhals), geht für die strukturierte goto case Variante.

Es gibt eine vierte Option.

einen Iterator Verwenden Sie eine Statemachine zu implementieren. Hier ist ein schöne kurze rel="nofollow Artikel zeigt Ihnen, wie

Es hat jedoch einige Nachteile. Manipulieren des Zustandes von außerhalb des Iterators ist nicht möglich.

Ich bin auch nicht sicher, ob es sehr schnell ist. Aber man kann immer einen Test.

Wenn Sie jemals Ihre Zustandsmaschine Übergangslogik in separate Funktionen brechen wollen, können Sie es nur tun switch-Anweisungen verwendet wird.

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

Es ist auch besser lesbar, und der Overhead der switch-Anweisung (im Vergleich zu Goto) nur einen Unterschied in der Leistung in seltenen Fällen machen.

EDIT:

Sie können "goto case" verwenden, um kleine Leistungsverbesserung zu machen:

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;
} 

Allerdings Sie das Risiko des Vergessens ausführen, um die Zustandsvariablen zu aktualisieren. Die später auf subtile Fehler verursachen könnten (weil Sie, dass „m_state“ angenommen wurde gesetzt), so würde ich vermeiden es vorschlagen.

Ich persönlich bevor zweite mit goto da zunächst einem unnötigen Schleife Schritt (zum Beispiel) erfordern wird auf den neuen Zustand zu gehen

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top