This is the third and final part in my series of articles about my .NET State Machine Toolkit. In Part I, I introduced the classes that make up the toolkit and demonstrated how to create a simple, flat state machine. In Part II, I discussed some of the advanced features of the toolkit and demonstrated how to create a hierarchical state machine. In this part, we will look at how to use code generation to create state machines.

Note: based on feedback, since originally submitting my article from Ramon Smits, I've vastly improved the toolkit's XML support. It now uses XML serialization directly instead of relying on a DataSet to read and write state machines as XML data. In addition, the XML schema has been greatly simplified. Many thanks to Ramon for his helpful suggestions and providing code to demonstrate his ideas.

Code generation is accomplished through the StateMachineBuilder class. This class follows the Builder design pattern. With this design pattern, an object is constructed in steps using a builder object. After all the necessary steps have been taken, the builder is instructed to build the object, usually by calling a Build method. After which the built object can be retrieved and used. This pattern helps break down the complex construction of an object into discrete steps. It also enables you to use the same construction process repeatedly to get different representations. With the StateMachineBuilder class, you build a CodeDom CodeNamespace object. The namespace contains an abstract class representing the state machine. This class serves as a base class for the class that you will write.

Originally, the StateMachineBuilder class used classes from ADO.NET for representing state machine data. Simple DataTables represented states, events, guards, etc. More complex DataTables represented state transitions, substate/superstate relationships, and so on. One-to-many relationships were established between the simple tables and the more complex tables. Basically, it was an in-memory relational database for representing state machines. Pretty nifty, or so I thought...

There were problems with this approach. The main one was that I couldn't enforce all of the rules for declaring a hierarchical state machine through data constraints alone. It was possible to enter illegal combinations of values in the tables. For example, you could declare a state to be a substate of one state and a superstate to that same state. Since a state cannot be a substate of one state and a superstate to the same state, this was nonsense. I was trying to make a relational database do the job of a compiler, and it wasn't working. In addition, the XML generated by the DataSet was overly verbose. A better approach was needed.

Instead of using a large number of DataTables to create a relational database, the StateMachineBuilder class now uses four custom classes for keeping track of states, events, guards, actions, transitions, etc. The classes are:

StateRow

StateRowCollection

TransitionRow

TransitionRowCollection

The StateRow class represents a single state. The StateRowCollection class represents a collection of StateRows. You can think of the StateRowCollection as a table of states. The TransitionRow and TransitionRowCollection classes together represent a state's transitions.

Let's look at the StateRow class first. It has the following properties:

Name

InitialState

HistoryType

Substates

Transitions

The Name property is the name of the state. The InitialState property is the state's initial state. If the state does not have any substates, this property is ignored when the state machine is built. The HistoryType property is the state's history type, obviously. This property is also ignored if the state does not have any substates. These three properties can be thought of as three columns in the state table.

The Substates property is interesting. It represents a StateRowCollection object. So if we can think of a collection of StateRows as belonging to a table, this property is a kind of table within a table. StateRows can be added to the Substates property, and in turn those StateRows can have StateRows added to their Substates property, and so on. This forms a tree like structure in which there is a top level of states, states that do not have a superstate, and branches descending from them representing their substates.

The Transitions property represents a TransitionRowCollection object. A state's transitions are added to this property. So each StateRow contains a table of its transitions.

Each of these classes have XML serialization attributes describing how they should be serialized as XML data. In addition, the StateRowCollection class and the TransitionRowCollection class can be data bound to a control such as the DataGrid. This makes it easy to create a GUI front end for creating state machines with the StateMachineBuilder class.

The StateMachineBuilder class has a States property representing a StateRowCollection object. It is the root of the state hierarchy. Once all of the states and their transitions have been added to the StateMachineBuilder, the state machine base class can be built. As stated earlier, the result is a CodeDom CodeNamespace object.

Yes, the code is ugly and verbose. This is due in part to the fully qualified names CodeDom is using. However, this is a code you never have to touch or look at. The class generated is the base class from which you derive your own state machine class. The advantage of this approach is that if you need to change the state machine, such as adding an event, you can regenerate the code and your derived class is not touched, only the base class is regenerated. You may need to make some minor tweaks to your derived class depending on what changes you make, but your implementation is not overwritten.

The entry and exit methods are made virtual with do-nothing implementations. This in effect makes them optional. In your derived class, if you need to add behavior for entry and/or exit actions, you can override the methods you need and implement the behavior. The guard and action methods, however, are abstract. You must override these.

Here is the new TrafficLight class. It is derived from the TrafficLightBase generated by the StateMachineBuilder:

The StateMachineBuilder class can be serialized as XML data. This lets you save state machine values and retrieve them later. Before looking at an XML representation of the traffic light state machine, let's look at the XML structure the toolkit uses to represent hierarchical state machines. We will examine each element and their attributes.

The root element is stateMachine, and it has three attributes:

namespace

name

initialState

The namespace attribute is the name of the namespace in which the state machine class resides. The name attribute is the name of the state machine. And the initialState attribute is the initial state of the state machine. The value of the initialState attribute must be one of the top level states. A top level state is a state that does not have a superstate; it exists at the top of the state hierarchy. Not surprisingly, states are represented by the state element. It has three attributes:

name

initialState

historyType

The name attribute is the name of the state. The initialState attribute is the initial state of the state; if the state has any substates, the initialState attribute represents which of its substates is entered after it is entered. And the historyType attribute represents the state's history type. It can have one of three values, None, Shallow, and Deep. If a state does not have any substates, the initialState and the historyType attributes are ignored. Otherwise the initialState attribute is required. The historyType attribute is optional, and if it is not present, the state will default to a history type value of None.

States can be nested inside other states. A nested state is the substate of the state that contains it, and it in turn can have nested states. Thus substate/superstate relationships are represented directly in the XML state machine structure.

State transitions are represented by the transition element. Transitions are nested inside the states to which they belong. The transition element has four attributes:

event

guard

action

target

The event attribute represents the event that triggered the transition. The guard attribute represents the guard that is evaluated to determine whether or not the transition should actually take place. The action attribute is the action that should be performed if the transition takes place. And the target attribute is the state target of the transition. All of the attributes are optional except for the event attribute. It must be present in all transitions.

To serialize a state machine, you would first build it with the StateMachineBuilder as we did above with the traffic light state machine. Then serialize the builder with the XmlSerializer class:

Included with the demo project is a program that provides a nice GUI for using the StateMachineBuilder class. It's easy to use. Simply enter the values into the DataGrid, build the state machine, and save the results as either C# or VB code. If you want to save the state machine values for editing later, you can save the data as an XML file. I'll explain how to use the State Machine Maker application below.

The State Machine Maker has three text boxes for setting the state machine's namespace, name, and initial state respectively. The important thing to note here is that you will get an error if you forget to enter an initial state. Every state machine must have an initial state it enters into when it is first run.

In addition, there is a DataGrid control where you add states and their transitions. The DataGrid is data bound to the StateMachineBuilder's States property so that entries made to the DataGrid are added to the StateMachineBuilder automatically. Initially, you will enter the top level states for the state machines; these are states that do not have a superstate.

After entering a top level state, you can add its substates by expanding its row and clicking the Substates link:

There you will be taken to its Substates table:

After adding the substates, you can navigate back to the State table by clicking on the navigation arrow:

A state's transitions are added the same way, only you click on the Transitions link. This takes you to the state's Transition table:

Once all of the states and their transitions have been added, you can build the state machine. An error message will be displayed if the build failed. For example, say that you forgot to enter a state's name:

If the build succeeded, you'll get a message letting you know:

After the build, you can save the results as C# or VB code:

When you save the results as code, it will save the results from the last build, not the last edit. In other words, be sure to remember to build the state machine immediately before saving it as code. You may make a change to the state machine after a build and forget this when saving it to code and wonder why your last edit isn't showing up.

Well, this wraps up the last article in the series. With the second version of the toolkit, I'm now comfortable with it overall. While the engine was something that I worked hard on and was satisfied with, aspects of the code generation process still felt rough around the edges to me. With some help from a fellow CP'ian, that is no longer the case. I now feel that support for code generation is up to the same level of quality as the rest of the toolkit. And I hope you find it useful. Thanks for your time.

Share

About the Author

Aside from dabbling in BASIC on his old Atari 1040ST years ago, Leslie's programming experience didn't really begin until he discovered the Internet in the late 90s. There he found a treasure trove of information about two of his favorite interests: MIDI and sound synthesis.

After spending a good deal of time calculating formulas he found on the Internet for creating new sounds by hand, he decided that an easier way would be to program the computer to do the work for him. This led him to learn C. He discovered that beyond using programming as a tool for synthesizing sound, he loved programming in and of itself.

Eventually he taught himself C++ and C#, and along the way he immersed himself in the ideas of object oriented programming. Like many of us, he gotten bitten by the design patterns bug and a copy of GOF is never far from his hands.

Now his primary interest is in creating a complete MIDI toolkit using the C# language. He hopes to create something that will become an indispensable tool for those wanting to write MIDI applications for the .NET framework.

Besides programming, his other interests are photography and playing his Les Paul guitars.