[C#] C #의 간단한 상태 머신 예제?

최신 정보:

예제에 다시 한 번 감사 드리며, 그들은 매우 도움이되었으며 다음과 같이 나는 그들로부터 아무것도 빼앗지 않을 것입니다.

내가 제시 한 예와 상태 머신을 이해하는 한, 상태 머신이 일반적으로 이해하는 것의 절반 만 현재 주어진 예제가 아닙니까?
예제는 상태를 변경하지만 변수 값을 변경하고 다른 상태에서 다른 값 변경을 허용하는 것으로 만 표현되는 반면, 일반적으로 상태 시스템은 동작을 변경하지 않아야합니다. 상태에 따라 변수에 대해 다른 값 변경을 허용한다는 의미이지만 다른 상태에 대해 다른 방법을 실행할 수 있다는 의미입니다.

또는 상태 시스템과 일반적인 용도에 대한 오해가 있습니까?

친애하는


원래 질문 :

C #의 상태 시스템 및 반복자 블록 과 상태 시스템을 만드는 도구 및 C #을위한 도구 대한이 토론을 찾았 으므로 많은 추상적 인 내용을 찾았지만 멍청한 놈에게는 약간 혼란 스럽습니다.

따라서 누군가가 3,4 개의 상태로 간단한 상태 머신을 실현하는 C # 소스 코드 예제를 제공 할 수 있다면 좋을 것입니다.




답변

이 간단한 상태 다이어그램으로 시작하겠습니다.

간단한 상태 머신 다이어그램

우리는 :

  • 4 가지 상태 (비활성, 활성, 일시 중지 및 종료)
  • 5 가지 유형의 상태 전환 (시작 명령, 종료 명령, 일시 중지 명령, 재개 명령, 종료 명령).

현재 상태 및 명령에 대해 switch 문을 수행하거나 전이 테이블에서 전이를 찾는 등의 몇 가지 방법으로이를 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상태 머신을 변이하는 기능.


답변

기존 오픈 소스 Finite State Machine 중 하나를 사용할 수 있습니다. 예를 들어 bbv.Common.StateMachine은 http://code.google.com/p/bbvcommon/wiki/StateMachine에 있습니다. 매우 직관적 인 유창한 구문과 진입 / 종료 동작, 전환 동작, 가드, 계층 적, 수동적 구현 (호출자의 스레드에서 실행) 및 활성 구현 (FSM이 실행되는 자체 스레드, 이벤트가 대기열에 추가됩니다).

Juliets를 예로 들어 상태 머신에 대한 정의는 매우 쉽습니다.

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으로 이동했습니다.


답변

다음은 매우 고전적인 유한 상태 기계의 예입니다 (TV와 같은 매우 단순화 된 전자 장치 모델링).

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


답변

여기에는 뻔뻔한 자체 프로모션이 있지만, 얼마 전에 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 . 이 구문은 일반적으로 데이터 시퀀스를 느리게 생성하는 데 사용되지만이 경우 실제로 반환 된 시퀀스 (모두 null 임)에 관심이 없지만 후드 아래에서 생성되는 상태 동작에 실제로 관심이 있습니다.

StateMachine기본 클래스는 각각 할당 코드 건설에 일부 반사하지 [Trigger]을 설정 조치를,Trigger 부재를 전진 상태 머신을 이동.

그러나 당신은 그것을 사용하기 위해 실제로 내부를 이해할 필요가 없습니다.


답변

코드 블록을 오케스트레이션 방식으로 실행할 수있는 반복자 블록을 코딩 할 수 있습니다. 코드 블록이 어떻게 분해되는지는 실제로 어떤 것에도 대응할 필요가 없으며, 코드를 코딩하려는 방식 일뿐입니다. 예를 들면 다음과 같습니다.

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가 표시됩니다. 보시다시피, 일이 어떻게 발생해야 하는지를 조정하는 데 유용한 메커니즘입니다.


답변

이것은 다른 관점에서 상태 머신이기 때문에 여기에 다른 답변을 게시하고 있습니다. 매우 시각적입니다.

내 원래의 대답은 고전적인 명령 코드입니다. 상태 머신을 간단하게 시각화하는 배열로 인해 코드가 진행되는 것처럼 보입니다. 단점은이 모든 것을 작성해야한다는 것입니다. Remos 의 답변은 보일러 플레이트 코드를 작성하는 노력을 덜어 주지만 훨씬 덜 시각적입니다. 세 번째 대안이 있습니다. 실제로 상태 머신을 그립니다.

.NET을 사용하고 런타임 버전 4를 대상으로 할 수있는 경우 워크 플로우의 상태 머신 활동 을 사용하는 옵션이 있습니다. 이것들은 본질적으로 상태 머신 ( 줄리엣 의 다이어그램 에서와 같이 )을 그리고 WF 런타임이 당신을 위해 그것을 실행하게합니다.

자세한 내용은 MSDN 문서 Windows Workflow Foundation 을 사용 하여 State Machines 빌드 및 최신 버전에 대한 CodePlex 사이트 를 참조하십시오.

.NET을 타겟팅 할 때 항상 프로그래머가 아닌 사람들에게 쉽게보고 변경하고 설명 할 수 있기 때문에 선호하는 옵션입니다. 그들이 말하는대로 그림은 천 단어의 가치가있다!


답변

상태 머신은 추상화이며,이를 생성하기 위해 특정 도구가 필요하지 않지만 도구가 유용 할 수 있음을 기억하는 것이 유용합니다.

예를 들어 함수가있는 상태 머신을 실현할 수 있습니다.

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

이 기계는 갈매기를 찾아 물 풍선으로 때리려 고합니다. 만약 놓치면 맞을 때까지 발사를 시도합니다 (실제로 기대할 수 있습니다). 그렇지 않으면 콘솔에서 화를냅니다. 갈매기가 괴롭힐 때까지 사냥을 계속합니다.

각 기능은 각 상태에 해당합니다. 시작 및 종료 (또는 수락 ) 상태는 표시되지 않습니다. 함수에 의해 모델링 된 것보다 더 많은 상태가있을 수 있습니다. 예를 들어 풍선을 발사 한 후 기계는 실제로 이전과 다른 상태에 있지만이 구별은 비실용적이라고 결정했습니다.

일반적인 방법은 클래스를 사용하여 상태를 표시 한 다음 다른 방식으로 연결하는 것입니다.