Приклад простої машини в C #?


257

Оновлення:

Ще раз дякую за приклади, вони були дуже корисними, і з наступним я не хочу нічого від них відбирати.

Чи не наведені в даний час приклади, наскільки я розумію їх та державні машини, лише половина того, що ми зазвичай розуміємо під державною машиною?
У тому сенсі, що приклади змінюють стан, але це представляється лише зміною значення змінної (і дозволяючи різним змінам змін у різних станах), в той час як зазвичай машина машини також повинна змінювати свою поведінку, а поведінку не (тільки) в сенс допускати різні зміни значення для змінної залежно від стану, але в сенсі дозволяти виконувати різні методи для різних станів.

Або у мене неправильне уявлення про державні машини та їх загальне використання?

З найкращими побажаннями


Оригінальне запитання:

Я знайшов цю дискусію щодо державних блоків та блоків ітераторів у c # та інструментах створення державних машин, а що не для C #, тому я знайшов багато абстрактних речей, але як noob все це трохи заплутано.

Тож було б чудово, якби хтось міг надати приклад вихідного коду C #, який реалізує просту машину стану з, можливо, 3,4 станами, просто щоб отримати суть цього.



Вам цікаво, що стосується державних машин взагалі чи лише таких, що базуються на ітераторі?
Скурмедель

2
Є .Net Core Stateless lib із прикладами, дайграмою DAGs тощо. - Варто переглянути: hanselman.com/blog/…
zmische

Відповіді:


416

Почнемо з цієї простої діаграми стану:

проста схема машини машини

Ми маємо:

  • 4 стани (неактивні, активні, призупинені та вихідні)
  • 5 типів переходів стану (команда "Почати команду", "Кінцева команда", "Призупинити команду", "Продовжити команду", "Вийти з команди".

Ви можете перетворити це на C # в декілька способів, таких як виконання оператора перемикання на поточний стан та команди або пошук переходів у таблиці переходів. Для цієї простої машини машини я віддаю перевагу таблиці переходу, яку дуже легко представити за допомогою Dictionary:

using System;
using System.Collections.Generic;

namespace Juliet
{
    public enum ProcessState
    {
        Inactive,
        Active,
        Paused,
        Terminated
    }

    public enum Command
    {
        Begin,
        End,
        Pause,
        Resume,
        Exit
    }

    public class Process
    {
        class StateTransition
        {
            readonly ProcessState CurrentState;
            readonly Command Command;

            public StateTransition(ProcessState currentState, Command command)
            {
                CurrentState = currentState;
                Command = command;
            }

            public override int GetHashCode()
            {
                return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                StateTransition other = obj as StateTransition;
                return other != null && this.CurrentState == other.CurrentState && this.Command == other.Command;
            }
        }

        Dictionary<StateTransition, ProcessState> transitions;
        public ProcessState CurrentState { get; private set; }

        public Process()
        {
            CurrentState = ProcessState.Inactive;
            transitions = new Dictionary<StateTransition, ProcessState>
            {
                { new StateTransition(ProcessState.Inactive, Command.Exit), ProcessState.Terminated },
                { new StateTransition(ProcessState.Inactive, Command.Begin), ProcessState.Active },
                { new StateTransition(ProcessState.Active, Command.End), ProcessState.Inactive },
                { new StateTransition(ProcessState.Active, Command.Pause), ProcessState.Paused },
                { new StateTransition(ProcessState.Paused, Command.End), ProcessState.Inactive },
                { new StateTransition(ProcessState.Paused, Command.Resume), ProcessState.Active }
            };
        }

        public ProcessState GetNext(Command command)
        {
            StateTransition transition = new StateTransition(CurrentState, command);
            ProcessState nextState;
            if (!transitions.TryGetValue(transition, out nextState))
                throw new Exception("Invalid transition: " + CurrentState + " -> " + command);
            return nextState;
        }

        public ProcessState MoveNext(Command command)
        {
            CurrentState = GetNext(command);
            return CurrentState;
        }
    }


    public class Program
    {
        static void Main(string[] args)
        {
            Process p = new Process();
            Console.WriteLine("Current State = " + p.CurrentState);
            Console.WriteLine("Command.Begin: Current State = " + p.MoveNext(Command.Begin));
            Console.WriteLine("Command.Pause: Current State = " + p.MoveNext(Command.Pause));
            Console.WriteLine("Command.End: Current State = " + p.MoveNext(Command.End));
            Console.WriteLine("Command.Exit: Current State = " + p.MoveNext(Command.Exit));
            Console.ReadLine();
        }
    }
}

Що стосується особистих уподобань, я люблю конструювати свої державні машини з GetNextфункцією детермінованого повернення наступного стану та MoveNextфункцією мутації станкової машини.


65
+1 для правильної реалізації GetHashCode()використання прайметів.
ja72

13
Не могли б ви пояснити мені призначення GetHashCode ()?
Сіддхарт

14
@Siddharth: StateTransitionКлас використовується як ключ у словнику і важливість рівності ключів. Два чіткі екземпляри StateTransitionслід вважати рівними, якщо вони являють собою один і той же перехід (наприклад, CurrentStateі Commandє однаковими). Для реалізації рівності ви повинні перевизначити Equals, а також GetHashCode. Зокрема, словник буде використовувати хеш-код, і два рівні об’єкти повинні повернути один і той же хеш-код. Ви також отримуєте хороші показники, якщо не дуже багато нерівних об'єктів мають один і той же хеш-код, тому GetHashCodeреалізуються як показано.
Мартін Ліверсайз

14
Хоча це, безумовно, отримує державну машину (і належну реалізацію C # 'також), я вважаю, що все ще не вистачає відповіді на питання ОП щодо зміни поведінки? Зрештою, він просто обчислює стани, але поведінка, пов’язана зі змінами стану, власне м'ясо програми і, як правило, називається події введення / виходу, все ще відсутнє.
Стейн

2
Якщо комусь це знадобиться: я налаштував цю машину тату і використав її у своїй грі єдності. Доступний на git hub: github.com/MarcoMig/Finite-State-Machine-FSM
Max_Power89

73

Можливо, ви хочете використовувати один з існуючих машин з кінцевим станом з відкритим кодом. Наприклад, bbv.Common.StateMachine знайдено на веб-сайті http://code.google.com/p/bbvcommon/wiki/StateMachine . Він має дуже інтуїтивно зрозумілий синтаксис і безліч функцій, таких як дії введення / виходу, дії переходу, захисти, ієрархічна, пасивна реалізація (виконується на потоці абонента) та активна реалізація (власний потік, на якому працює fsm, події додаються до черги).

Беручи до прикладу Джульєттів, визначення державного апарату стає дуже простим:

var fsm = new PassiveStateMachine<ProcessState, Command>();
fsm.In(ProcessState.Inactive)
   .On(Command.Exit).Goto(ProcessState.Terminated).Execute(SomeTransitionAction)
   .On(Command.Begin).Goto(ProcessState.Active);
fsm.In(ProcessState.Active)
   .ExecuteOnEntry(SomeEntryAction)
   .ExecuteOnExit(SomeExitAction)
   .On(Command.End).Goto(ProcessState.Inactive)
   .On(Command.Pause).Goto(ProcessState.Paused);
fsm.In(ProcessState.Paused)
   .On(Command.End).Goto(ProcessState.Inactive).OnlyIf(SomeGuard)
   .On(Command.Resume).Goto(ProcessState.Active);
fsm.Initialize(ProcessState.Inactive);
fsm.Start();

fsm.Fire(Command.Begin);

Оновлення : Місце розташування проекту переміщено до: https://github.com/appccelerate/statemachine


4
Дякуємо за посилання на цю чудову машину з відкритим кодом. Чи можу я запитати, як я можу отримати поточний стан?
Рамазан Полат

2
Ти не можеш і не повинен. Держава - це щось нестабільне. Коли ви запитуєте державу, можливо, ви перебуваєте в середині переходу. Усі дії слід робити в межах переходів, в'їзду до держави та виходів із держави. Якщо ви дійсно хочете мати стан, ви можете додати місцеве поле та призначити стан в дії введення.
Ремо Слава

4
Питання в тому, для чого вам це потрібно? Наприклад, якщо вам потрібен текст відображення, то кілька заявлених можуть мати один і той же відображуваний текст, наприклад, якщо підготовка до відправки має кілька підзарядних станів. У цьому випадку ви повинні робити саме те, що маєте намір зробити. Оновіть деякий текст відображення в потрібних місцях. Наприклад, у ExecuteOnEntry. Якщо вам потрібна додаткова інформація, тоді задайте нове запитання та вкажіть саме вашу проблему, оскільки тут не йдеться про тему.
Ремо Слава

Гаразд, я задаю нове запитання і чекаю, коли ви відповісте. Тому що я не думаю, що хтось інший вирішить цю проблему, оскільки ти маєш найкращу відповідь, але все-таки запитувач не прийняв. Я опублікую тут URL-адресу запитання. Дякую.
Рамазан Полат

4
+1 для вільного та декларативного API. Це приголомшливо. До речі, код google здається застарілим. Їх найновіший сайт проекту знаходиться на GitHub тут
KFL

51

Ось приклад дуже класичної машини з кінцевим станом, що моделює дуже спрощений електронний пристрій (наприклад, телевізор)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace fsm
{
class Program
{
    static void Main(string[] args)
    {
        var fsm = new FiniteStateMachine();
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.PlugIn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOff);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.RemovePower);
        Console.WriteLine(fsm.State);
        Console.ReadKey();
    }

    class FiniteStateMachine
    {
        public enum States { Start, Standby, On };
        public States State { get; set; }

        public enum Events { PlugIn, TurnOn, TurnOff, RemovePower };

        private Action[,] fsm;

        public FiniteStateMachine()
        {
            this.fsm = new Action[3, 4] { 
                //PlugIn,       TurnOn,                 TurnOff,            RemovePower
                {this.PowerOn,  null,                   null,               null},              //start
                {null,          this.StandbyWhenOff,    null,               this.PowerOff},     //standby
                {null,          null,                   this.StandbyWhenOn, this.PowerOff} };   //on
        }
        public void ProcessEvent(Events theEvent)
        {
            this.fsm[(int)this.State, (int)theEvent].Invoke();
        }

        private void PowerOn() { this.State = States.Standby; }
        private void PowerOff() { this.State = States.Start; }
        private void StandbyWhenOn() { this.State = States.Standby; }
        private void StandbyWhenOff() { this.State = States.On; }
    }
}
}

6
для всіх, хто не є новим у державних машинах, це відмінний перший приклад, щоб спочатку промокнути ноги.
PositiveGuy

2
Я новачок у державних машинах і серйозно, це принесло мені Світло - дякую!
MC5

1
Мені сподобалась ця реалізація. Для кожного, хто може натрапити на це, невелике «покращення». У класі FSM я додав private void DoNothing() {return;}і замінив усі екземпляри null на this.DoNothing. Має приємний побічний ефект повернення поточного стану.
Sethmo011

1
Мені цікаво, чи є міркування за деякими з цих імен. Коли я дивлюся на це, моя перша інтуїція - перейменувати елементи Statesна Unpowered, Standby, On. Мої міркування полягають у тому, що якби хтось запитав мене, у якому стані знаходиться моє телебачення, я б сказав "Вимкнено", а не "Почати". Я також змінився StandbyWhenOnі StandbyWhenOffдо TurnOnі TurnOff. Це робить код читати більш інтуїтивно, але мені цікаво, чи існують конвенції чи інші фактори, які роблять мою термінологію менш придатною.
Джейсон Хамже

Здається розумним, я насправді не дотримувався жодної конвенції про іменування державою; назвати як має сенс для будь-якого моделювання.
Піт Стеншнес

20

Тут якимось безсоромним саморекламою, але деякий час тому я створив бібліотеку під назвою YieldMachine, яка дозволяє дуже чітко та просто описати державну машину з обмеженою складністю. Наприклад, розглянемо світильник:

державна машина світильника

Зауважте, що ця державна машина має 2 тригери та 3 стану. У коді YieldMachine ми пишемо єдиний метод для всієї поведінки, пов’язаної з державою, в якій ми здійснюємо жахливу жорстокість використання gotoдля кожної держави. Тригер стає властивістю або полем типу Action, прикрашеним атрибутом під назвою Trigger. Я прокоментував код першого стану та його переходи нижче; наступні стани дотримуються тієї ж схеми.

public class Lamp : StateMachine
{
    // Triggers (or events, or actions, whatever) that our
    // state machine understands.
    [Trigger]
    public readonly Action PressSwitch;

    [Trigger]
    public readonly Action GotError;

    // Actual state machine logic
    protected override IEnumerable WalkStates()
    {
    off:                                       
        Console.WriteLine("off.");
        yield return null;

        if (Trigger == PressSwitch) goto on;
        InvalidTrigger();

    on:
        Console.WriteLine("*shiiine!*");
        yield return null;

        if (Trigger == GotError) goto error;
        if (Trigger == PressSwitch) goto off;
        InvalidTrigger();

    error:
        Console.WriteLine("-err-");
        yield return null;

        if (Trigger == PressSwitch) goto off;
        InvalidTrigger();
    }
}

Короткий і приємний, е-е!

Ця державна машина управляється просто шляхом надсилання на неї тригерів:

var sm = new Lamp();
sm.PressSwitch(); //go on
sm.PressSwitch(); //go off

sm.PressSwitch(); //go on
sm.GotError();    //get error
sm.PressSwitch(); //go off

Для уточнення я додав кілька коментарів до першого стану, щоб допомогти вам зрозуміти, як це використовувати.

    protected override IEnumerable WalkStates()
    {
    off:                                       // Each goto label is a state

        Console.WriteLine("off.");             // State entry actions

        yield return null;                     // This means "Wait until a 
                                               // trigger is called"

                                               // Ah, we got triggered! 
                                               //   perform state exit actions 
                                               //   (none, in this case)

        if (Trigger == PressSwitch) goto on;   // Transitions go here: 
                                               // depending on the trigger 
                                               // that was called, go to
                                               // the right state

        InvalidTrigger();                      // Throw exception on 
                                               // invalid trigger

        ...

Це працює, тому що компілятор C # фактично створив внутрішньодержавну машину для кожного методу, який використовує yield return. Ця конструкція зазвичай використовується для лінивого створення послідовностей даних, але в цьому випадку нас насправді не цікавить повернута послідовність (яка все одно є нульовою), а поведінка стану, яке створюється під кришкою.

StateMachineБазовий клас робить деякі роздуми про будівництво для присвоєння коду для кожного [Trigger]дії, яке встановлює Triggerелемент і переміщає державну машину вперед.

Але вам не потрібно розуміти внутрішні, щоб мати можливість ним користуватися.


2
"Гото" жорстокий лише в тому випадку, якщо він стрибає між методами. Це, на щастя, заборонено в C #.
Браннон

Гарна думка! Насправді, я був би дуже вражений, якби будь-яка статично набрана мова зуміла дозволити gotoміж методами.
skrebbel

3
@Brannon: яка мова дозволяє gotoпереходити між методами? Я не бачу, як це могло б працювати. Ні, gotoце проблематично, оскільки це призводить до процесуального програмування (це само по собі ускладнює приємні речі, такі як тестування одиниць), сприяє повторенню коду (помічено, як InvalidTriggerпотрібно вставити для кожного стану?) І, нарешті, ускладнює подачу програми. Порівняйте це з (більшістю) іншими рішеннями в цій темі, і ви побачите, що це єдине, де вся FSM відбувається в одному методі. Цього зазвичай достатньо, щоб викликати стурбованість.
Гроо

1
Наприклад, @Groo, GW-BASIC. Це допомагає, що у нього немає методів чи навіть функцій. Крім того, у мене дуже важко зрозуміти, чому в цьому прикладі ви вважаєте, що "потоку програми важче слідувати". Це державна машина, "перехід" до стану з іншого - це єдине, що ти робиш. Це gotoдосить добре.
skrebbel

3
GW-BASIC дозволяє gotoпереходити між функціями, але він не підтримує функції? :) Ви маєте рацію, зауваження "важче дотримуватися" - це загальне gotoпитання, адже це не велика проблема в даному випадку.
Гроо

13

Ви можете кодувати ітераторний блок, який дозволяє виконувати блок коду в оркестрованому вигляді. Розбиття блоку коду насправді не повинно відповідати нічому, це просто так, як ви хочете його кодувати. Наприклад:

IEnumerable<int> CountToTen()
{
    System.Console.WriteLine("1");
    yield return 0;
    System.Console.WriteLine("2");
    System.Console.WriteLine("3");
    System.Console.WriteLine("4");
    yield return 0;
    System.Console.WriteLine("5");
    System.Console.WriteLine("6");
    System.Console.WriteLine("7");
    yield return 0;
    System.Console.WriteLine("8");
    yield return 0;
    System.Console.WriteLine("9");
    System.Console.WriteLine("10");
}

У цьому випадку, коли ви телефонуєте CountToTen, поки що нічого не виконується. Що ви отримуєте, це фактично генератор стан машин, для якого ви можете створити новий екземпляр державної машини. Виконуєте це, зателефонувавши GetEnumerator (). Отриманий IEnumerator фактично є машиною стану, яку можна керувати, зателефонувавши MoveNext (...).

Таким чином, у цьому прикладі, коли ви вперше зателефонуєте на MoveNext (...), ви побачите "1", записане на консоль, а в наступний раз, коли ви зателефонуєте MoveNext (...), ви побачите 2, 3, 4 та потім 5, 6, 7, а потім 8, а потім 9, 10. Як бачите, це корисний механізм оркестрування того, як мають відбуватися справи.


6
Обов'язкова посилання на справедливе попередження
sehe

8

Я розміщую тут ще одну відповідь, оскільки це державні машини з іншого погляду; дуже візуально.

Моя оригінальна відповідь - класичний імперативний код. Я думаю, що це досить візуально як код іде через масив, який робить візуалізацію стану машини простою. Мінус у тому, що ви повинні все це написати. Відповідь Ремоса зменшує зусилля щодо написання коду на плиті котла, але набагато менш візуально. Є третя альтернатива; дійсно малюючи державну машину.

Якщо ви використовуєте .NET і можете націлити версію 4 часу виконання, то у вас є можливість використовувати стан робочого процесу в машині . Вони, по суті, дозволяють вам намалювати стан машини (так само, як на діаграмі Джульєтти ) і змусити час роботи WF виконати його за вас.

Докладніші відомості див. У статті MSDN " Створення державних машин із фундаментом робочого циклу Windows" та на останній версії цього сайту CodePlex .

Це варіант, який я завжди віддаю перевагу при націлюванні на .NET, оскільки його легко бачити, змінювати та пояснювати непрограмістам; картини варті тисячі слів, як кажуть!


Я думаю, що державна машина - одна з найкращих частин усього фундаменту робочого процесу!
fabsenet

7

Корисно пам’ятати, що державні машини - це абстракція, і для її створення не потрібні певні інструменти, однак інструменти можуть бути корисними.

Наприклад, ви можете реалізувати стан машини з функціями:

void Hunt(IList<Gull> gulls)
{
    if (gulls.Empty())
       return;

    var target = gulls.First();
    TargetAcquired(target, gulls);
}

void TargetAcquired(Gull target, IList<Gull> gulls)
{
    var balloon = new WaterBalloon(weightKg: 20);

    this.Cannon.Fire(balloon);

    if (balloon.Hit)
    {
       TargetHit(target, gulls);
    }
    else
       TargetMissed(target, gulls);
}

void TargetHit(Gull target, IList<Gull> gulls)
{
    Console.WriteLine("Suck on it {0}!", target.Name);
    Hunt(gulls);
}

void TargetMissed(Gull target, IList<Gull> gulls)
{
    Console.WriteLine("I'll get ya!");
    TargetAcquired(target, gulls);
}

Ця машина полюватиме на чайок і намагатиметься вразити їх повітряними кулями. Якщо він пропустить, він спробує запустити одного, доки він не вдариться (це може зробити з деякими реалістичними очікуваннями;)), інакше він заграє в консолі. Він продовжує полювати до тих пір, поки не зникне з чайок.

Кожна функція відповідає кожному стану; стани початку та кінця (або прийняти ) не відображаються. Напевно, там є більше станів, ніж моделюють функції. Наприклад, після вистрілення повітряної кулі машина справді перебуває в іншому стані, ніж раніше, але я вирішив, що цю відмінність робити недоцільно.

Поширений спосіб - використовувати класи для представлення станів, а потім з'єднувати їх різними способами.


7

Знайшов цей чудовий підручник в Інтернеті, і він допоміг мені обернути голову навколо машин обмеженого стану.

http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867

Підручник - це агностик мови, тому його можна легко адаптувати до потреб C #.

Також використаний приклад (мурашник шукає їжу) легко зрозуміти.


З підручника:

введіть тут опис зображення

public class FSM {
    private var activeState :Function; // points to the currently active state function

    public function FSM() {
    }

    public function setState(state :Function) :void {
        activeState = state;
    }

    public function update() :void {
        if (activeState != null) {
            activeState();
        }
    }
}


public class Ant
{
    public var position   :Vector3D;
    public var velocity   :Vector3D;
    public var brain      :FSM;

    public function Ant(posX :Number, posY :Number) {
        position    = new Vector3D(posX, posY);
        velocity    = new Vector3D( -1, -1);
        brain       = new FSM();

        // Tell the brain to start looking for the leaf.
        brain.setState(findLeaf);
    }

    /**
    * The "findLeaf" state.
    * It makes the ant move towards the leaf.
    */
    public function findLeaf() :void {
        // Move the ant towards the leaf.
        velocity = new Vector3D(Game.instance.leaf.x - position.x, Game.instance.leaf.y - position.y);

        if (distance(Game.instance.leaf, this) <= 10) {
            // The ant is extremelly close to the leaf, it's time
            // to go home.
            brain.setState(goHome);
        }

        if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) {
            // Mouse cursor is threatening us. Let's run away!
            // It will make the brain start calling runAway() from
            // now on.
            brain.setState(runAway);
        }
    }

    /**
    * The "goHome" state.
    * It makes the ant move towards its home.
    */
    public function goHome() :void {
        // Move the ant towards home
        velocity = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y);

        if (distance(Game.instance.home, this) <= 10) {
            // The ant is home, let's find the leaf again.
            brain.setState(findLeaf);
        }
    }

    /**
    * The "runAway" state.
    * It makes the ant run away from the mouse cursor.
    */
    public function runAway() :void {
        // Move the ant away from the mouse cursor
        velocity = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y);

        // Is the mouse cursor still close?
        if (distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) {
            // No, the mouse cursor has gone away. Let's go back looking for the leaf.
            brain.setState(findLeaf);
        }
    }

    public function update():void {
        // Update the FSM controlling the "brain". It will invoke the currently
        // active state function: findLeaf(), goHome() or runAway().
        brain.update();

        // Apply the velocity vector to the position, making the ant move.
        moveBasedOnVelocity();
    }

    (...)
}

1
Хоча це посилання може відповісти на питання, краще включити сюди суттєві частини відповіді та надати посилання для довідки. Відповіді лише на посилання можуть стати недійсними, якщо пов’язана сторінка зміниться. - З огляду
drneel

@drneel Я можу скопіювати та вставити біти з підручника ... але хіба це не буде брати кредит у автора?
Jet Blue

1
@JetBlue: Залиште посилання у відповіді в якості посилання та додайте відповідні біти у власних словах у відповідь, щоб не порушувати нікого авторських прав. Я знаю, що це здається суворим, але багато відповідей стали набагато, набагато кращими через це правило.
Flimm

6

Сьогодні я заглиблююся в модель дизайну держави. Я зробив і протестував ThreadState, що дорівнює (+/-) Threading у C #, як описано на малюнку з Threading in C #

введіть тут опис зображення

Ви можете легко додавати нові стани, конфігурувати переходи з одного стану в інший дуже просто, оскільки він інкапсульований у реалізацію стану

Реалізація та використання за адресою: Реалізує .NET ThreadState за державним дизайном


1
Посилання мертва. У вас є інший?
рулони

5

Я ще не намагався реалізувати FSM в C #, але ці всі звуки (або виглядають) дуже складні в тому, як я обробляв FSM в минулому на мовах низького рівня, таких як C або ASM.

Я вважаю, що метод, який я завжди знав, називається чимось на зразок "ітеративної петлі". У ній у вас, по суті, є цикл "while", який періодично виходить на основі подій (переривань), а потім знову повертається в основний цикл.

В обробниках переривань слід передати CurrentState і повернути NextState, який потім перезаписує змінну CurrentState в основному циклі. Ви робите це нескінченне оголошення до тих пір, поки програма не закриється (або мікроконтролер не скинеться назад).

Що я бачу інші відповіді, все виглядає дуже складно порівняно з тим, як FSM, на мій погляд, призначений для впровадження; його краса полягає в його простоті, і FSM може бути дуже складним з багатьма, багатьма станами і переходами, але вони дозволяють складний процес легко зруйнувати та засвоїти.

Я усвідомлюю, що моя відповідь не повинна містити іншого питання, але я змушений запитати: чому ці інші запропоновані рішення здаються настільки складними?
Вони, схоже, схожі на те, що вдарили по маленькому цвяху гігантським кувалдою.


1
Повністю згоден. Простий цикл у той час як оператор перемикання настільки ж простий, як ви можете отримати.
булочки

2
Якщо у вас є дуже складна державна машина з багатьма станами та умовами, де ви могли б отримати кілька вкладених комутаторів. Також може бути штраф за зайняте очікування, залежно від вашого циклу виконання.
Sune Rievers

3

Що за державаПаттерн. Чи відповідає це вашим потребам?

Я думаю, що його контекст пов'язаний, але це варто зняти точно.

http://en.wikipedia.org/wiki/State_pattern

Це нехай ваші штати вирішують, куди йти, а не клас "об'єкт".

Бруно


1
Модель стану стосується класу, який може діяти по-різному, залежно від стану / режиму, в якому він перебуває, він не стосується переходу між станами.
Елі Алгранті

3

На мою думку, державна машина призначена не лише для зміни станів, але й (дуже важлива) для обробки тригерів / подій у конкретному стані. Якщо ви хочете краще зрозуміти модель дизайну штатних машин, хороший опис можна знайти в книзі " Шаблони проектування головного дизайну", сторінка 320 .

Йдеться не тільки про стани всередині змінних, але й про обробку тригерів у різних станах. Чудовий розділ (і ні, для мене немає жодної плати за згадку про це :-), який містить просто зрозуміле пояснення.


3

Я щойно сприяв цьому:

https://code.google.com/p/ysharp/source/browse/#svn%2Ftrunk%2FStateMachinesPoC

Ось один із прикладів демонстрації прямої та непрямої передачі команд із станами як IObserver (сигналу), таким чином, реагуючи на джерело сигналу, IObservable (of signal):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    using Machines;

    public static class WatchingTvSampleAdvanced
    {
        // Enum type for the transition triggers (instead of System.String) :
        public enum TvOperation { Plug, SwitchOn, SwitchOff, Unplug, Dispose }

        // The state machine class type is also used as the type for its possible states constants :
        public class Television : NamedState<Television, TvOperation, DateTime>
        {
            // Declare all the possible states constants :
            public static readonly Television Unplugged = new Television("(Unplugged TV)");
            public static readonly Television Off = new Television("(TV Off)");
            public static readonly Television On = new Television("(TV On)");
            public static readonly Television Disposed = new Television("(Disposed TV)");

            // For convenience, enter the default start state when the parameterless constructor executes :
            public Television() : this(Television.Unplugged) { }

            // To create a state machine instance, with a given start state :
            private Television(Television value) : this(null, value) { }

            // To create a possible state constant :
            private Television(string moniker) : this(moniker, null) { }

            private Television(string moniker, Television value)
            {
                if (moniker == null)
                {
                    // Build the state graph programmatically
                    // (instead of declaratively via custom attributes) :
                    Handler<Television, TvOperation, DateTime> stateChangeHandler = StateChange;
                    Build
                    (
                        new[]
                        {
                            new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = stateChangeHandler },
                            new { From = Television.Unplugged, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.SwitchOn, Goto = Television.On, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.SwitchOff, Goto = Television.Off, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler }
                        },
                        false
                    );
                }
                else
                    // Name the state constant :
                    Moniker = moniker;
                Start(value ?? this);
            }

            // Because the states' value domain is a reference type, disallow the null value for any start state value : 
            protected override void OnStart(Television value)
            {
                if (value == null)
                    throw new ArgumentNullException("value", "cannot be null");
            }

            // When reaching a final state, unsubscribe from all the signal source(s), if any :
            protected override void OnComplete(bool stateComplete)
            {
                // Holds during all transitions into a final state
                // (i.e., stateComplete implies IsFinal) :
                System.Diagnostics.Debug.Assert(!stateComplete || IsFinal);

                if (stateComplete)
                    UnsubscribeFromAll();
            }

            // Executed before and after every state transition :
            private void StateChange(IState<Television> state, ExecutionStep step, Television value, TvOperation info, DateTime args)
            {
                // Holds during all possible transitions defined in the state graph
                // (i.e., (step equals ExecutionStep.LeaveState) implies (not state.IsFinal))
                System.Diagnostics.Debug.Assert((step != ExecutionStep.LeaveState) || !state.IsFinal);

                // Holds in instance (i.e., non-static) transition handlers like this one :
                System.Diagnostics.Debug.Assert(this == state);

                switch (step)
                {
                    case ExecutionStep.LeaveState:
                        var timeStamp = ((args != default(DateTime)) ? String.Format("\t\t(@ {0})", args) : String.Empty);
                        Console.WriteLine();
                        // 'value' is the state value that we are transitioning TO :
                        Console.WriteLine("\tLeave :\t{0} -- {1} -> {2}{3}", this, info, value, timeStamp);
                        break;
                    case ExecutionStep.EnterState:
                        // 'value' is the state value that we have transitioned FROM :
                        Console.WriteLine("\tEnter :\t{0} -- {1} -> {2}", value, info, this);
                        break;
                    default:
                        break;
                }
            }

            public override string ToString() { return (IsConstant ? Moniker : Value.ToString()); }
        }

        public static void Run()
        {
            Console.Clear();

            // Create a signal source instance (here, a.k.a. "remote control") that implements
            // IObservable<TvOperation> and IObservable<KeyValuePair<TvOperation, DateTime>> :
            var remote = new SignalSource<TvOperation, DateTime>();

            // Create a television state machine instance (automatically set in a default start state),
            // and make it subscribe to a compatible signal source, such as the remote control, precisely :
            var tv = new Television().Using(remote);
            bool done;

            // Always holds, assuming the call to Using(...) didn't throw an exception (in case of subscription failure) :
            System.Diagnostics.Debug.Assert(tv != null, "There's a bug somewhere: this message should never be displayed!");

            // As commonly done, we can trigger a transition directly on the state machine :
            tv.MoveNext(TvOperation.Plug, DateTime.Now);

            // Alternatively, we can also trigger transitions by emitting from the signal source / remote control
            // that the state machine subscribed to / is an observer of :
            remote.Emit(TvOperation.SwitchOn, DateTime.Now);
            remote.Emit(TvOperation.SwitchOff);
            remote.Emit(TvOperation.SwitchOn);
            remote.Emit(TvOperation.SwitchOff, DateTime.Now);

            done =
                (
                    tv.
                        MoveNext(TvOperation.Unplug).
                        MoveNext(TvOperation.Dispose) // MoveNext(...) returns null iff tv.IsFinal == true
                    == null
                );

            remote.Emit(TvOperation.Unplug); // Ignored by the state machine thanks to the OnComplete(...) override above

            Console.WriteLine();
            Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done);

            Console.WriteLine();
            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }
    }
}

Зауважте: цей приклад є досить штучним і здебільшого має на меті демонструвати ряд ортогональних рис. Рідко повинна виникати реальна потреба в реалізації самого домену значень стану за допомогою повного роздутого класу, використовуючи CRTP (див. Http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern ).

Ось напевно простіший і, ймовірно, набагато більш поширений випадок використання реалізації (використовуючи простий тип перерахунку як домен значення штатів), для тієї ж машини машини та з тим же тестовим випадком:

https://code.google.com/p/ysharp/source/browse/trunk/StateMachinesPoC/WatchingTVSample.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    using Machines;

    public static class WatchingTvSample
    {
        public enum Status { Unplugged, Off, On, Disposed }

        public class DeviceTransitionAttribute : TransitionAttribute
        {
            public Status From { get; set; }
            public string When { get; set; }
            public Status Goto { get; set; }
            public object With { get; set; }
        }

        // State<Status> is a shortcut for / derived from State<Status, string>,
        // which in turn is a shortcut for / derived from State<Status, string, object> :
        public class Device : State<Status>
        {
            // Executed before and after every state transition :
            protected override void OnChange(ExecutionStep step, Status value, string info, object args)
            {
                if (step == ExecutionStep.EnterState)
                {
                    // 'value' is the state value that we have transitioned FROM :
                    Console.WriteLine("\t{0} -- {1} -> {2}", value, info, this);
                }
            }

            public override string ToString() { return Value.ToString(); }
        }

        // Since 'Device' has no state graph of its own, define one for derived 'Television' :
        [DeviceTransition(From = Status.Unplugged, When = "Plug", Goto = Status.Off)]
        [DeviceTransition(From = Status.Unplugged, When = "Dispose", Goto = Status.Disposed)]
        [DeviceTransition(From = Status.Off, When = "Switch On", Goto = Status.On)]
        [DeviceTransition(From = Status.Off, When = "Unplug", Goto = Status.Unplugged)]
        [DeviceTransition(From = Status.Off, When = "Dispose", Goto = Status.Disposed)]
        [DeviceTransition(From = Status.On, When = "Switch Off", Goto = Status.Off)]
        [DeviceTransition(From = Status.On, When = "Unplug", Goto = Status.Unplugged)]
        [DeviceTransition(From = Status.On, When = "Dispose", Goto = Status.Disposed)]
        public class Television : Device { }

        public static void Run()
        {
            Console.Clear();

            // Create a television state machine instance, and return it, set in some start state :
            var tv = new Television().Start(Status.Unplugged);
            bool done;

            // Holds iff the chosen start state isn't a final state :
            System.Diagnostics.Debug.Assert(tv != null, "The chosen start state is a final state!");

            // Trigger some state transitions with no arguments
            // ('args' is ignored by this state machine's OnChange(...), anyway) :
            done =
                (
                    tv.
                        MoveNext("Plug").
                        MoveNext("Switch On").
                        MoveNext("Switch Off").
                        MoveNext("Switch On").
                        MoveNext("Switch Off").
                        MoveNext("Unplug").
                        MoveNext("Dispose") // MoveNext(...) returns null iff tv.IsFinal == true
                    == null
                );

            Console.WriteLine();
            Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done);

            Console.WriteLine();
            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }
    }
}

'HTH


Хіба не дивно, що кожен екземпляр штату має свою копію графіка стану?
Гроо

@Groo: ні, ні. Тільки екземпляри телебачення, побудовані за допомогою приватного конструктора з нульовим рядком для монікера (отже, виклик захищеного методу "Build") матимуть графік стану як державні машини. Решта, названі випадки телебачення (з прізвищем НЕ нульовий для цього звичайних і нерегламентованих мети) буде всього лише «точковим фікс» станів (так би мовити), службовці в якості констант стану (що граф станів (и) фактичні стан машини будуть називатися їх вершинами). 'HTH,
YSharp

Гаразд, я розумію. У будь-якому випадку, IMHO, було б краще, якби ви включили якийсь код, який насправді обробляє ці переходи. Таким чином, він слугує лише прикладом використання не так очевидного інтерфейсу для вашої бібліотеки (IMHO). Наприклад, як StateChangeвирішується? Через рефлексію? Це справді потрібно?
Гроо

1
@Groo: Добре зауваження. Насправді, не потрібно замислюватися над обробником у цьому першому прикладі, тому що це робиться там програмно точно і може бути статично пов'язане / тип перевірений (на відміну від випадків за допомогою спеціальних атрибутів). Тож ця робота, як і очікувалося: private Television(string moniker, Television value) { Handler<Television, TvOperation, DateTime> myHandler = StateChange; // (code omitted) new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = myHandler } }
YSharp

1
Дякуємо за ваші зусилля!
Гроо

3

Я зробив цю загальну державну машину з коду Джульєтти. Це працює для мене приголомшливо.

Це переваги:

  • ви можете створити нову державну машину в коді з двома перерахуваннями TStateі TCommand,
  • додана структура, TransitionResult<TState>щоб мати більше контролю над результатами виведення [Try]GetNext()методів
  • викриття вкладеного класу StateTransition лише шляхом AddTransition(TState, TCommand, TState)полегшення роботи з ним

Код:

public class StateMachine<TState, TCommand>
    where TState : struct, IConvertible, IComparable
    where TCommand : struct, IConvertible, IComparable
{
    protected class StateTransition<TS, TC>
        where TS : struct, IConvertible, IComparable
        where TC : struct, IConvertible, IComparable
    {
        readonly TS CurrentState;
        readonly TC Command;

        public StateTransition(TS currentState, TC command)
        {
            if (!typeof(TS).IsEnum || !typeof(TC).IsEnum)
            {
                throw new ArgumentException("TS,TC must be an enumerated type");
            }

            CurrentState = currentState;
            Command = command;
        }

        public override int GetHashCode()
        {
            return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            StateTransition<TS, TC> other = obj as StateTransition<TS, TC>;
            return other != null
                && this.CurrentState.CompareTo(other.CurrentState) == 0
                && this.Command.CompareTo(other.Command) == 0;
        }
    }

    private Dictionary<StateTransition<TState, TCommand>, TState> transitions;
    public TState CurrentState { get; private set; }

    protected StateMachine(TState initialState)
    {
        if (!typeof(TState).IsEnum || !typeof(TCommand).IsEnum)
        {
            throw new ArgumentException("TState,TCommand must be an enumerated type");
        }

        CurrentState = initialState;
        transitions = new Dictionary<StateTransition<TState, TCommand>, TState>();
    }

    /// <summary>
    /// Defines a new transition inside this state machine
    /// </summary>
    /// <param name="start">source state</param>
    /// <param name="command">transition condition</param>
    /// <param name="end">destination state</param>
    protected void AddTransition(TState start, TCommand command, TState end)
    {
        transitions.Add(new StateTransition<TState, TCommand>(start, command), end);
    }

    public TransitionResult<TState> TryGetNext(TCommand command)
    {
        StateTransition<TState, TCommand> transition = new StateTransition<TState, TCommand>(CurrentState, command);
        TState nextState;
        if (transitions.TryGetValue(transition, out nextState))
            return new TransitionResult<TState>(nextState, true);
        else
            return new TransitionResult<TState>(CurrentState, false);
    }

    public TransitionResult<TState> MoveNext(TCommand command)
    {
        var result = TryGetNext(command);
        if(result.IsValid)
        {
            //changes state
            CurrentState = result.NewState;
        }
        return result;
    }
}

Це тип повернення методу TryGetNext:

public struct TransitionResult<TState>
{
    public TransitionResult(TState newState, bool isValid)
    {
        NewState = newState;
        IsValid = isValid;
    }
    public TState NewState;
    public bool IsValid;
}

Як використовувати:

Ось як можна створити OnlineDiscountStateMachineз загального класу:

Визначте перерахунок OnlineDiscountStateдля його станів та перерахунок OnlineDiscountCommandдля його команд.

Визначте клас, OnlineDiscountStateMachineпохідний від родового класу, використовуючи ці два перерахунки

Виведіть конструктор base(OnlineDiscountState.InitialState)так, що початковий стан встановлено наOnlineDiscountState.InitialState

Використовуйте AddTransitionстільки разів, скільки потрібно

public class OnlineDiscountStateMachine : StateMachine<OnlineDiscountState, OnlineDiscountCommand>
{
    public OnlineDiscountStateMachine() : base(OnlineDiscountState.Disconnected)
    {
        AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Connected);
        AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Error_AuthenticationError);
        AddTransition(OnlineDiscountState.Connected, OnlineDiscountCommand.Submit, OnlineDiscountState.WaitingForResponse);
        AddTransition(OnlineDiscountState.WaitingForResponse, OnlineDiscountCommand.DataReceived, OnlineDiscountState.Disconnected);
    }
}

використовувати похідну державну машину

    odsm = new OnlineDiscountStateMachine();
    public void Connect()
    {
        var result = odsm.TryGetNext(OnlineDiscountCommand.Connect);

        //is result valid?
        if (!result.IsValid)
            //if this happens you need to add transitions to the state machine
            //in this case result.NewState is the same as before
            Console.WriteLine("cannot navigate from this state using OnlineDiscountCommand.Connect");

        //the transition was successfull
        //show messages for new states
        else if(result.NewState == OnlineDiscountState.Error_AuthenticationError)
            Console.WriteLine("invalid user/pass");
        else if(result.NewState == OnlineDiscountState.Connected)
            Console.WriteLine("Connected");
        else
            Console.WriteLine("not implemented transition result for " + result.NewState);
    }

1

Я думаю, що в машині стану, запропонованій Джульєттом, є помилка: метод GetHashCode може повернути один і той же хеш-код для двох різних переходів, наприклад:

State = Active (1), Command = Пауза (2) => HashCode = 17 + 31 + 62 = 110

Стан = Призупинено (2), Команда = Кінець (1) => HashCode = 17 + 62 + 31 = 110

Щоб уникнути цієї помилки, метод повинен бути таким:

public override int GetHashCode()
   {
            return 17 + 23 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
   }

Олексій


1
Код хешу не потрібно повертати унікальне число для будь-якої можливої ​​комбінації, лише виразне значення з хорошим розподілом по цільовому діапазону (у цьому випадку діапазон - це всі можливі intзначення). Ось чому HashCodeзавжди реалізується разом з Equals. Якщо хеш-коди однакові, то об'єкти перевіряються на точність eqaulity за допомогою Equalsметоду.
Дмитро Автономов

0

FiniteStateMachine - це проста державна машина, написана на C # Link

Переваги використання моєї бібліотеки FiniteStateMachine:

  1. Визначте "контекстний" клас для представлення єдиного інтерфейсу для зовнішнього світу.
  2. Визначте державний абстрактний базовий клас.
  3. Представити різні "стани" державної машини як похідні класи базового класу State.
  4. Визначте специфічну для держави поведінку у відповідних похідних класах.
  5. Підтримуйте вказівник на поточний "стан" у класі "контекст".
  6. Щоб змінити стан державної машини, змініть поточний покажчик "стану".

Завантажити DLL завантажити

Приклад на LINQPad:

void Main()
{
            var machine = new SFM.Machine(new StatePaused());
            var output = machine.Command("Input_Start", Command.Start);
            Console.WriteLine(Command.Start.ToString() + "->  State: " + machine.Current);
            Console.WriteLine(output);

            output = machine.Command("Input_Pause", Command.Pause);
            Console.WriteLine(Command.Pause.ToString() + "->  State: " + machine.Current);
            Console.WriteLine(output);
            Console.WriteLine("-------------------------------------------------");
}
    public enum Command
    {
        Start,
        Pause,
    }

    public class StateActive : SFM.State
    {

        public override void Handle(SFM.IContext context)

        {
            //Gestione parametri
            var input = (String)context.Input;
            context.Output = input;

            //Gestione Navigazione
            if ((Command)context.Command == Command.Pause) context.Next = new StatePaused();
            if ((Command)context.Command == Command.Start) context.Next = this;

        }
    }


public class StatePaused : SFM.State
{

     public override void Handle(SFM.IContext context)

     {

         //Gestione parametri
         var input = (String)context.Input;
         context.Output = input;

         //Gestione Navigazione
         if ((Command)context.Command == Command.Start) context.Next = new  StateActive();
         if ((Command)context.Command == Command.Pause) context.Next = this;


     }

 }

1
Він має ліцензію GNU GPL.
Der_Meister

0

Я б рекомендував state.cs . Я особисто використовував state.js (версію JavaScript) і дуже задоволений цим. Ця версія C # працює аналогічно.

Ви обґрунтовано заявляєте:

        // create the state machine
        var player = new StateMachine<State>( "player" );

        // create some states
        var initial = player.CreatePseudoState( "initial", PseudoStateKind.Initial );
        var operational = player.CreateCompositeState( "operational" );
        ...

Ви створюєте декілька переходів:

        var t0 = player.CreateTransition( initial, operational );
        player.CreateTransition( history, stopped );
        player.CreateTransition<String>( stopped, running, ( state, command ) => command.Equals( "play" ) );
        player.CreateTransition<String>( active, stopped, ( state, command ) => command.Equals( "stop" ) );

Ви визначаєте дії щодо станів та переходів:

    t0.Effect += DisengageHead;
    t0.Effect += StopMotor;

І це (в значній мірі) це. Перегляньте веб-сайт для отримання додаткової інформації.



0

Інша альтернатива цього репо https://github.com/lingkodsoft/StateBliss використовує вільний синтаксис, підтримує тригери.

    public class BasicTests
    {
        [Fact]
        public void Tests()
        {
            // Arrange
            StateMachineManager.Register(new [] { typeof(BasicTests).Assembly }); //Register at bootstrap of your application, i.e. Startup
            var currentState = AuthenticationState.Unauthenticated;
            var nextState = AuthenticationState.Authenticated;
            var data = new Dictionary<string, object>();

            // Act
            var changeInfo = StateMachineManager.Trigger(currentState, nextState, data);

            // Assert
            Assert.True(changeInfo.StateChangedSucceeded);
            Assert.Equal("ChangingHandler1", changeInfo.Data["key1"]);
            Assert.Equal("ChangingHandler2", changeInfo.Data["key2"]);
        }

        //this class gets regitered automatically by calling StateMachineManager.Register
        public class AuthenticationStateDefinition : StateDefinition<AuthenticationState>
        {
            public override void Define(IStateFromBuilder<AuthenticationState> builder)
            {
                builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
                    .Changing(this, a => a.ChangingHandler1)
                    .Changed(this, a => a.ChangedHandler1);

                builder.OnEntering(AuthenticationState.Authenticated, this, a => a.OnEnteringHandler1);
                builder.OnEntered(AuthenticationState.Authenticated, this, a => a.OnEnteredHandler1);

                builder.OnExiting(AuthenticationState.Unauthenticated, this, a => a.OnExitingHandler1);
                builder.OnExited(AuthenticationState.Authenticated, this, a => a.OnExitedHandler1);

                builder.OnEditing(AuthenticationState.Authenticated, this, a => a.OnEditingHandler1);
                builder.OnEdited(AuthenticationState.Authenticated, this, a => a.OnEditedHandler1);

                builder.ThrowExceptionWhenDiscontinued = true;
            }

            private void ChangingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                var data = changeinfo.DataAs<Dictionary<string, object>>();
                data["key1"] = "ChangingHandler1";
            }

            private void OnEnteringHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                // changeinfo.Continue = false; //this will prevent changing the state
            }

            private void OnEditedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnExitedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnEnteredHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnEditingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
            }

            private void OnExitingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
            }

            private void ChangedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {
            }
        }

        public class AnotherAuthenticationStateDefinition : StateDefinition<AuthenticationState>
        {
            public override void Define(IStateFromBuilder<AuthenticationState> builder)
            {
                builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
                    .Changing(this, a => a.ChangingHandler2);

            }

            private void ChangingHandler2(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                var data = changeinfo.DataAs<Dictionary<string, object>>();
                data["key2"] = "ChangingHandler2";
            }
        }
    }

    public enum AuthenticationState
    {
        Unauthenticated,
        Authenticated
    }
}

0

Ви можете використовувати моє рішення, це найзручніший спосіб. Це також безкоштовно.

Створіть державну машину в три етапи:

1. Створіть схему в редакторі вузлів🔗 та завантажте її у своєму проекті за допомогою бібліотеки📚

StateMachine stateMachine = новий StateMachine ("схема.xml");

2. Опишіть логіку програми на події⚡

stateMachine.GetState ("Стан1"). OnExit (Action1);
stateMachine.GetState ("Стан2"). OnEntry (Action2);
stateMachine.GetTransition ("Transition1"). OnInvoke (Action3);
stateMachine.OnChangeState (Action4);

3. Запустіть державну машину🚘

stateMachine.Start ();

Посилання:

Редактор вузлів: https://github.com/SimpleStateMachine/SimpleStateMachineNodeEditor

Бібліотека: https://github.com/SimpleStateMachine/SimpleStateMachineLibrary

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.