Click here to Skip to main content
Click here to Skip to main content
Go to top

State Pattern in C#

, 3 Mar 2010
Rate this:
Please Sign up or sign in to vote.
A C# implementation of the Gang of Four State Pattern using features like Generics, enumeration, and Reflection to make life easier.

Introduction

This article presents a C# implementation of the Gang of Four State Pattern using features like Generics, enumeration, and Reflection to make life easier. The reader is assumed to be familiar with UML State Charts and the Gang of Four State Pattern; this article is not intended as a tutorial on those subjects.

Motivation

I regularly use UML State Charts to design state behavior. UML State Charts are very useful in discussions with users. When it comes to implementing, I like to use the Gang of Four State Pattern. The pattern enables a clear translation of a State Chart into code. But a couple of things annoyed me with the standard implementations:

  • StateContext needs to be updated in several places when adding concrete states.
  • Intellisense/auto-complete is not really helpful when setting a property of abstract type.
  • Concrete states need constructors with a reference to the StateContext. This contradicts with the clear translation of State Chart into code, and is distracting for the casual reader, especially while state classes can be very small.

These issues are addressed in a neat little package called fsm4net. Let's have a look.

Example

Let's dive in. Suppose we need to implement the following State Chart. We have a light that we need to turn on and off with a toggle event. And furthermore, the light should be turned off after a timeout. This simple example uses states, transitions, triggers, actions, and timeout.

ExampleStateChart.jpg

Using fsm4net, the implementation of our concrete states looks like this (actual code):

enum States { OnState, OffState, EmptyState }

class OffState : StateBase
{
    public override void EntryAction()
    {
        Context.Light.Off();
    }

    protected override void OnToggle()
    {
        Context.Next = States.OnState;
    }
}


class OnState : StateBase
{
    public override void EntryAction()
    {
        Context.Timeout = TimeSpan.FromSeconds(3);
        Context.Light.On();
    }

    protected override void  OnToggle()
    {
        Context.Next = States.OffState;
    }

    public override void TimeoutHandler()
    {
        Context.Next = States.OffState;
    }
}

What I really like about this code is that it translates very well to the State Chart and is also understandable for non-programmers. If you like it so far, then keep reading.

Overview

We'll take a look at the big picture first and then we'll get back to the example.

fsm4netUsage.jpg

This class diagram shows what you get when using fsm4net, i.e., inherit the abstract base classes and interface of the fsm4net assembly. The classes will look familiar if you've seen the State Pattern before. Let's go over them:

StatesEnum is an enumeration that identifies the concrete states in your machine and the empty or final state that is used for termination. It is very convenient while entering state change code to be able to select values from an enum using intellisense/autocomplete.

enum StatesEnum { ConcreteState1, ConcreteState2, Empty }

StateBase is the abstract base class for all your concrete states. The simplest form is:

abstract class StateBase : State<statecontext,>
{
}

StateBase is typically expanded with virtual handlers for specific triggers, as we'll see in the example later on.

StateContext is the bookkeeper. The base constructor takes two parameters: the enum values of the first state and the final state. The simplest form is:

interface IStateContext : IStateContext<statesenum> { }

class StateContext : StateContext<statesenum>, IStateContext
{
    public StateContext() 
        : base(StatesEnum.ConcreteState1, StatesEnum.Empty)
    {
    }
}

StateContext can be reached from every state using their inherited Context property. Typically, StateContext is expanded with properties that allow for either persisting data across states or accessing interfaces from states (Light, in our example).

IstateContext provides an interface for the thread that runs the state machine. The simplest implementation looks like:

IMyStateContext machine = new MyStateContext();
while (machine.IsActive)
{
    machine.Handle();
}

Every time Handle() is called, it will either:

  • Switch state (when a Next state is set) (calling ExitAction() on the current state, and EntryAction() on the new state)
  • Notify timeout (when timeout is elapsed) (calling TimeoutHandler() on the current state)
  • Notify trigger (when a trigger event is queued) (calling TriggerHandler() on the current state)
  • Otherwise: Call DoAction() on the current state

An example of a simple concrete state:

class ConcreteState1 : StateBase
{
    public override void EntryAction()
    {
        Context.Next = States.EmptyState;
    }
}

Look mum, no constructor (distractor)!

Details

The base constructor of StateContext performs all the magic, and uses Reflection to find out which states are implemented; instantiates the concrete states and matches them to StatesEnum. Some rules for the definition of states apply:

  • Every concrete state needs a corresponding enum value of the exact same name.
  • One extra enum value needs to be defined for the exit or terminating state, and cannot have an associated class.
  • No more rules Smile | :)

StateContext contains an EventArgs Queue to allow for thread-safe handling of events / triggers.

void EnqueueTriggerEventHandler(object sender, EventArgs e);

The enqueue method has the EventHandler signature, and can therefore directly subscribe to events. The sender object will be discarded.

StateContext contains a Timeout property of type TimeSpan that can be used for state timeouts. Timeouts are typically set in the EntryAction of a concrete state. Timeouts are reset when state is changed. When a timeout occurs, the TimeoutHandler() on the current state is called. The default action is to throw a NotImplementedException("Unhandled timeout").

Back to the example

We have already seen the concrete state implementations of our example state chart. Now, let's go over the other classes.

ExampleClassDiagram.jpg

IStateContext is the interface for the thread that runs the state machine. All members are inherited:

interface IStateContext : IStateContext<statesenum /> { }</statesenum /> 

StateContext publishes the ILight interface so states can control the light. Furthermore, StateContext subscribes to the OnToggle events:

class StateContext : StateContext<states>, IStateContext
{
    private ILight m_Light;

    public StateContext(ILight light)
        : base(States.OffState, States.EmptyState)
    {
        light.OnToggle += new EventHandler(EnqueueTriggerEventHandler);
        m_Light = light;
    }

    public ILight Light 
    {
        get { return m_Light; }
    }
}

And finally, StateBase will translate the triggers into OnToggle calls.

abstract class StateBase : State<statecontext,>
{
    public override void TriggerHandler(EventArgs e)
    {
        OnToggle();
    }

    protected virtual void OnToggle() { }
}

Our example is very simple. Typically, TriggerHandler will check the type of the argument and possibly its properties to distribute the events to separate method calls.

Conclusion

fsm4net provides a neat way to implement UML State Charts in C# using the GoF State Pattern. There is a good separation between functionality and implementation details, and intellisense / auto-complete really helps with the coding of the states. I hope it will be useful to you too.

History

  • 1.00: Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Martin de Liefde
Software Developer (Senior) PROMEXX Technical Automation B.V.
Netherlands Netherlands
Senior Software engineer specialized in embedded software and machine control. Like to play my guitars or work in the studio.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberJerry van Kranenburg22-Jan-13 23:19 
QuestionThank you for this article PinmemberWhiz-Bang! Computers30-Jul-12 10:48 
Questiondatabase implementation...? PinmemberColinBashBash3-Mar-10 8:33 
AnswerRe: database implementation...? PinmemberColinBashBash3-Mar-10 9:07 
AnswerRe: database implementation...? PinmemberMartin de Liefde3-Mar-10 9:30 
GeneralRe: database implementation...? PinmemberMartin de Liefde3-Mar-10 9:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140922.1 | Last Updated 3 Mar 2010
Article Copyright 2010 by Martin de Liefde
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid