Простой пример состояния машины в С#?

Обновление:

Снова спасибо за примеры, они были очень полезны, и со следующим я не имею в виду взять что-нибудь от них.

Не представлены ли в настоящее время примеры, насколько я понимаю их и государственные машины, только половину того, что мы обычно понимаем государственной машиной?
В том смысле, что примеры изменяют состояние, но это только представлено изменением значения переменной (и допускающей разные изменения значений в разных состояниях), в то время как обычно машина состояния также должна изменять ее поведение, а поведение не (только) в смысл допускать изменение разных значений для переменной в зависимости от состояния, но в смысле разрешения различных методов для разных состояний.

Или у меня есть неправильное представление о государственных машинах и их общее использование?

С наилучшими пожеланиями


Оригинальный вопрос:

Я нашел это обсуждение состояний машин и блоков итератора в С# и инструментов для создания государственных машин, а что не для С#, поэтому я нашел много абстрактных материалов, но как noob, все это немного запутывает.

Итак, было бы здорово, если бы кто-то мог представить пример исходного кода на С#, который реализует простой конечный автомат с возможно 3,4 состояниями, чтобы получить его суть.


Ответ 1

Начнем с этой простой диаграммы состояний:

простая диаграмма состояний машины

Имеем:

  • 4 состояния (неактивные, активные, приостановленные и завершенные)
  • 5 типов переходов состояний (команда начала, команда завершения, команда приостановки, команда возобновления, команда выхода).

Вы можете преобразовать это в С# несколькими способами, такими как выполнение оператора switch в текущем состоянии и команде или поиск переходов в таблице переходов. Для этого простого конечного автомата я предпочитаю таблицу переходов, которую очень легко представить с помощью 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 для возврата следующего состояния детерминистически и a MoveNext, чтобы мутировать конечный автомат.

Ответ 2

Возможно, вы захотите использовать один из существующих конечных автоматов с открытым исходным кодом. Например. 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

Ответ 3

Вот пример очень классического конечного автомата, моделирующего очень упрощенное электронное устройство (например, телевизор)

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

Ответ 4

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

state machine of a lamp

Обратите внимание, что этот конечный автомат имеет 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

        ...

Это работает, потому что компилятор С# фактически создал конечный автомат для каждого метода, который использует yield return. Эта конструкция обычно используется для ленивого создания последовательностей данных, но в этом случае мы фактически не заинтересованы в возвращенной последовательности (которая все равно равна нулю), а в поведении состояния, которое создается под капотом.

Базовый класс StateMachine делает некоторое размышление о конструкции для назначения кода для каждого действия [Trigger], которое устанавливает член Trigger и перемещает машину состояния вперед.

Но вам не нужно понимать внутренности, чтобы иметь возможность использовать его.

Ответ 5

Вы можете запрограммировать блок итератора, который позволяет выполнять блок кода в организованном порядке. Как разбить блок кода действительно не нужно что-либо соответствовать, просто как вы хотите его закодировать. Например:

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

Я публикую здесь другой ответ, так как это конечные автоматы с другой точки зрения; очень наглядно.

Мой оригинальный ответ - классический неуязвимый код. Я думаю, что это довольно наглядно, поскольку код идет из-за массива, который делает визуализацию конечного автомата простой. Недостатком является то, что вы должны написать все это. Ответ Remos облегчает написание кода, но гораздо менее нагляден. Есть третья альтернатива; действительно рисовать конечный автомат.

Если вы используете .NET и можете использовать версию 4 среды выполнения, у вас есть возможность использовать действия конечного автомата рабочего процесса. По сути, это позволяет вам нарисовать конечный автомат (как на диаграмме Джульетты) и заставить среду выполнения WF выполнить его за вас.

Дополнительную информацию см. в статье MSDN Создание конечных автоматов с помощью Windows Workflow Foundation и этого сайта CodePlex для получения последней версии.

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

Ответ 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);
}

Эта машина будет охотиться за чаек и попытаться ударить их воздушными шарами. Если он промахивается, он попытается выстрелить один, пока он не достигнет (может сделать с некоторыми реалистичными ожиданиями;)), иначе он будет злорадствовать в консоли. Он продолжает охотиться до тех пор, пока из чаек не будет издеваться.

Каждая функция соответствует каждому состоянию; состояния начала и конца (или принятия) не отображаются. Там, вероятно, больше состояний, чем моделируются функциями. Например, после запуска шара машина действительно находится в другом состоянии, чем раньше, но я решил, что это различие нецелесообразно.

Общим способом является использование классов для представления состояний, а затем их соединение по-разному.

Ответ 8

Сегодня я в глубине государства Design Pattern. Я сделал и протестировал ThreadState, который равен (+ / -) Threading в С#, как описано на рисунке из Threading в С#

enter image description here

Вы можете легко добавлять новые состояния, настраивать переходы из одного состояния в другое очень легко, потому что оно включено в реализацию состояний

Реализация и использование в: Реализация .NET ThreadState по шаблону State State

Ответ 9

Нашел этот отличный учебник в Интернете, и он помог мне обернуть голову в конечные автоматы.

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

Учебное пособие не зависит от языка, поэтому его можно легко адаптировать к вашим потребностям в С#.

Кроме того, используемый пример (муравей ищет еду) легко понять.


Из учебника:

enter image description here

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 time
            // to go home.
            brain.setState(goHome);
        }

        if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) {
            // Mouse cursor is threatening us. Let 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 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 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();
    }

    (...)
}

Ответ 10

Я еще не пробовал реализовать FSM на С#, но все эти звуки (или посмотрите) очень сложны для того, как я работал с FSM в прошлом на языках низкого уровня, таких как C или ASM.

Я считаю, что метод, который я всегда знал, называется чем-то вроде "Итеративной петли". В нем у вас по существу есть цикл while, который периодически выходит на основе событий (прерываний), а затем снова возвращается к основному циклу.

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

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

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

Ответ 11

Какой бой StatePattern. Соответствует ли это вашим потребностям?

Я думаю, что его контекст связан, но его стоит наверняка сделать.

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

Это позволяет вашим штатам решать, куда идти, а не классу "объект".

Бруно

Ответ 12

Я только что внесли свой вклад:

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

Здесь один из примеров демонстрации прямой и косвенной отправки команд с состояниями как IObserver (сигнала), поэтому реагирует на источник сигнала IObservable (сигнала):

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 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 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 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 state '{0}' a final state? {1}", tv.Value, done);

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

'НТН

Ответ 13

Я сделал этот универсальный конечный автомат из кода Джульетты. Это работает потрясающе для меня.

Вот эти преимущества:

  • Вы можете создать новый конечный автомат в коде с двумя перечислениями 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 из универсального класса:

Определите enum OnlineDiscountState для его состояний и enum OnlineDiscountCommand для его команд.

Определите класс OnlineDiscountStateMachine, производный от универсального класса, используя эти два перечисления

Извлеките конструктор из base(OnlineDiscountState.InitialState), чтобы начальное состояние initial state было установлено на 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);
    }

Ответ 14

По-моему, конечный автомат предназначен не только для изменения состояний, но также (очень важный) для обработки триггеров/событий в определенном состоянии. Если вы хотите лучше понять структуру шаблона государственного аппарата, хорошее описание можно найти в книге Head First Design Patterns, страница 320.

Речь идет не только о состояниях внутри переменных, но и об обработке триггеров в разных состояниях. Великая глава (и нет, нет никакой платы за меня, говоря об этом:-), который содержит только легкое для понимания объяснение.

Ответ 15

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

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

State = Paused (2), Command = End (1) = > HashCode = 17 + 62 + 31 = 110

Чтобы избежать этой ошибки, метод должен выглядеть следующим образом:

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

Алекс

Ответ 16

FiniteStateMachine - это простая машина состояния, написанная на С# Ссылка

Преимущества использования моей библиотеки FiniteStateMachine:

  • Определите класс "context", чтобы представить один интерфейс для внешнего мира.
  • Определить базовый класс абстрактного состояния.
  • Представлять различные "состояния" конечного автомата как производные классы базового класса состояний.
  • Определить поведение, специфичное для состояния, в соответствующих классах, связанных с состоянием.
  • Сохранять указатель на текущее "состояние" в классе "context".
  • Чтобы изменить состояние конечного автомата, измените текущий указатель состояния.

Загрузить 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;


     }

 }

Ответ 17

Я бы рекомендовал state.cs. Я лично использовал state.js(версия JavaScript) и очень доволен этим. Эта версия С# работает аналогичным образом.

Вы создаете состояния:

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

И это (в значительной степени) это. Посмотрите на сайт для получения дополнительной информации.

Ответ 18

В NuGet есть 2 популярных пакета государственных пакетов.

Appccelerate.StateMachine (13,6 тыс. загрузок + 3,82 тыс. устаревшей версии (bbv.Common.StateMachine))

StateMachineToolkit (загрузка 1.56K)

В Appccelerate lib есть хорошая документация, но она не поддерживает .NET 4, поэтому я выбрал StateMachineToolkit для моего проекта.

Ответ 19

Другая альтернатива в этом репо 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
    }
}