This post shows how to easily implement states in C# using a T4 template.
Background
I often run into situations where I need an object that has a number of alternate “states” or “choices”. The set of states must be treated as “one thing” for general manipulation, but on the other hand, each state will often have slightly different behavior from the other states.
Some examples:
- A shopping cart might have states
Empty
,Active
andPaid
.- You can call “AddItem” but not “RemoveItem” on the
Empty
state - You can call “AddItem” and “RemoveItem” and also “Pay” on the
Active
state - You can’t do anything with the
Paid
state. Once paid, the cart is immutable.
- You can call “AddItem” but not “RemoveItem” on the
- A domain object such as
Product
might have statesValid
andInvalid
.- Only
Valid
objects can be saved to disk.
- Only
- A game such as chess might have states
WhiteToPlay
,BlackToPlay
andGameOver
.- Only the
GameOver
state has a “Winner” method. - Only the other states have a “Play” method.
- Only the
So given this common situation, what kinds of ways are there to implement these kinds of cases?
The inheritance based approach
The standard approach in a object-oriented language is to use inheritance. There is a class for each state, and each state inherits from a common base class or interface.
But where should the custom behavior live?
For example, in the shopping cart example, should the “RemoveItem” method be available at the interface level?
Approach 1: Define all possible actions at the interface level
Let’s say the “RemoveItem” method is available at the interface level, then the interface would look like this:
1 2 3 4 5 6 |
|
But what should the empty state implementation do with the methods that are not relevant? It can either throw an exception or ignore them, as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Throwing an exception seems a bit drastic, and ignoring the call seems even worse. Is there another approach?
Approach 2: Define actions only at the appropriate level
The next approach is that the base interface should only have genuinely common code, and each subclass implements its own special behavior.
So the code for the shopping cart would look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
This is much cleaner, but now the question is: how does the client know which state the cart is in?
Typically, the caller would have to try downcasting the object to each state in turn. The client code would look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
But this approach is not only ugly, it’s all error prone, as the client has to do all the work.
For example, what happens if the client forgets to cast properly? And what happens if the requirements change and there are now four states to handle? These kinds of errors will be hard to catch.
Approach 3: The double dispatch or visitor pattern
In OO design, a reliance on this kind of branching and casting for control flow is always a sign that polymorphism is not being used properly. And indeed there is a way to avoid downcasting by using so-called “double dispatch” or its big brother, the visitor pattern.
The idea behind it is that the caller does not know the type of the state, but the state knows its own type, and can call back a type-specific method on the caller. For example, the empty state can call a “VisitEmpty” method, while the active state can call a “VisitActive” method, and so on. In this way, no casting is used. Furthermore, if the number of states increase, the code will break until a handler for the new state is implemented.
To use this approach, first we implement a visitor interface as follows:
1 2 3 4 5 6 |
|
Next we change the base interface to allow the visitor to call on the state:
1 2 3 4 |
|
Finally, for each state, we implement the appropriate VisitXXX
method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Now we are ready to use it – and here is where our problems start! Because, for each different set of visitor behavior, we have to implement a custom class.
For example, when we want to add an item, we have to create an instance of AddProductVisitor
and when we want to pay for something, we need to create a PayForCartVisitor
.
Here’s some code to show what I mean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Phew! It does work, and it is polymorphic and type safe. But it seems like an awful lot of scaffolding is needed just to do something straightforward. An interface, two new classes, and nine new methods!
Here’s are all the players visualized as a diagram:
Compared with the second approach, you can see that it is a lot more complicated. And this is a simple example! It reminds me of the ”Kingdom of Nouns”.
Surely there must be another way – combining the simplicity of the second approach with the safeness of the visitor approach?
Algebraic data types and “Sum” types
Let’s step back and see how other languages do it. In some languages, especially functional languages such as Haskell and F#, the type system is not based around classes, but instead is based around mathematics.
The idea is that given a number of smaller types, you can combine them in various ways to make larger, composite types. There are two ways of creating these combinations, “addition” and “multiplication”, and the kinds of types built this way are called “algebraic data types”.
Multiplication of types means taking every possible value of one type and combining it with every possible value of another type. You might already be familiar with this from SQL, because it is similar to a CROSS JOIN. The resulting composite types are called “product” types.
For example a Date
type might be created by combining every possible Day
value with every possible Month
value with every possible Year
value . So the type is written Date type = Day type x Month type x Year type
.
On the other hand, addition of types means having a set of types and choosing only one value at a time from the union of all of possible values.
For example, we could define a new Temperature
type as EITHER one value from the set of temperatures in DegreesF
OR one value from the set of temperatures in DegreesC
. The resulting composite types are called “sum” or “union” types. So the temperature type is written Temperature type = DegreesF type + DegreesC type
.
What does that mean in practice? It means that “sum” types are perfect for modelling situations where there are mutually exclusive states or choices.
In F#, for example, we can create types for each shopping cart state, and then combine (“sum”) them into a composite type. Here’s the F# code:
1 2 3 4 5 6 7 8 9 10 |
|
In F#, a CartState
must be one of the possible choices. That is, it can be Empty
or Active
or Paid
, but only one at any one time.
And because of this, it is simple to write code that depends on the particular choice. For example, to add a product to the cart, the client code would look something like this:
1 2 3 4 5 |
|
You can think of match cart with
code as a kind of switch statement that has a different case for each state. Each case is a sort of lambda expression, which in C# would look something like:
1 2 3 |
|
The particular state that the object is in determines which lambda expression to execute.
Here is the similar code for removing a product from the cart:
1 2 3 4 5 6 7 8 9 10 |
|
and so on.
For more details on how F# does this, see my post on the F# implementation of a shopping cart.
Implementing choices in C#
The F# code gives us a clue on how we might solve the problem in C#.
The idea is that for each possible state, we pass in a lambda specifically designed for that state. We do this through a single method call. In other words we provide a list of functions but we don’t know which one will actually get evaluated – we let the cart decide, based on its state.
Here is some example code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
And here is an example of how a client might call it in practice:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
As you can see, this approach solves all the problems discussed earlier:
- It is completely idiot proof. A client of the interface always gets a valid value back. The client cannot mess things up by forgetting to cast or forgetting to check for null.
- Illegal states are not even representable. A client cannot call the “wrong” method, such as calling “Pay” on an empty cart. The compiler will not allow it. Because of this, there is no need for runtime errors or “not implemented” exceptions.
- If the number of states ever changes to four say, just add a new parameter to the
Transition
method. TheTransition
method will now take four lambdas, so all your existing code will fail to compile. This a good thing! You cannot accidentally forget to handle a state. - The code is simple and low complexity. Just as in the second approach above, each state only implements the methods it needs to, and there are no
if
statements or special case handlers anywhere.
In a way, this is what the visitor pattern was trying to get at in its complicated way, but the use of inline lambdas rather than whole visitor classes reduces the complexity immensely.
Introducing the T4 template
Now that we understand how the states work in conjunction with the top level interface, we can create them in C#.
But there is a lot of boilerplate – so I have created a T4 template that generate the states for you automatically.
Creating new types using the T4 template
To setup, first download the FoldStates.ttinclude and add it to your project somewhere.
Then to create a new type:
- Create a template file and give the filename the name of the interface type, such as “ICartState.tt”
- Include the code below, and define each state that you want in the array, as shown.
1 2 3 4 |
|
Visual Studio will then generate you a file with code like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
|
Using the new type
A few things to note about the generated code:
- There are three generated methods:
Transition
,Action
andFunc
. I’ll explain the purpose of each one shortly. - All the classes are partial. After creating the states, you should then create separate files for the partial classes so you can write some real code!
For the shopping cart example, let’s look at each state in turn.
The CartStateEmpty state
As described above, you should create a separate file CartStateEmpty.cs
and create code like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
This state has no items. The only thing you can do with this state is add a product.
To add a product, create a new list of products and pass it into the constructor of the CartStateActive
class.
Note that the Add
method returns an ICartState
not a CartStateActive
. This is critical! The caller should not know the exact state of the cart, just that it is some kind of cart.
You’ll see why this is so important shortly.
The CartStateActive state
Again, you should create a separate file CartStateActive.cs
and create code like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
This state does have a list of items, and supports three methods: Add
, Remove
, and Pay
.
To add a product, create a new list of products and pass it into the constructor of the CartStateActive
class. Again note that the Add
method returns an ICartState
not a CartStateActive
.
To remove a product, remove it from the existing list of products and pass either into the constructor of the CartStateActive
class, or if the list is empty, return a new CartStateEmpty
.
Again note that this method returns an ICartState
. The (ICartState)
cast is needed to make the ternary expression happy, unfortunately.
Finally, the Pay
method creates a CartStatePaid
and returns it. Once again, the return type is ICartState
.
The CartStatePaid state
Finally, implement CartStatePaid.cs
with code like this:
1 2 3 4 5 6 7 8 9 10 11 |
|
CartStatePaid
is immutable – you can get the list of items and payment amount, but you can’t change them.
Now that you have seen all the implementations, it is worth pointing out another great benefit of this approach. In the implementation for each state, there are hardly any conditionals or branches! You’ll find that this state-based approach really reduces code complexity because each method only has to deal with one state.
Putting it together with some tests.
Lets see how this code can be used in practice.
First, lets start with a simple test case: “If you add a item to an empty cart you get an active cart with one item”.
Here’s the code for this test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
You can see here that we are using the Func
method and passing in three lambdas. To test what state the cart is in, we return true or false for each lambda.
And to count the items, we return a int.
Note that if we had the following code, we would get an compiler error:
1 2 3 4 5 |
|
The reason is that cartStateEmpty
does not have an Items
property. This is a key concept – you cannot call a method accidentally using this approach.
The caller cannot forget to cast, or forget to handle a state. The interface enforces this.
Let’s look at another test: “If you pay for an active cart you get an paid cart with the same number of items and the same amount paid”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
Note that in the last assertion, we are getting the cartStatePaid.Amount
. This property does not exist for the other two states, and as above, if we accidentally try to call it, we will get a compiler error.
So you have seen how the Func
method works. The Action
method is exactly the same, but returns void
instead.
The Transition method
Now let’s look at the Transition
method and see how it is used.
Here is the code that adds a product to a cart.
1 2 3 4 5 6 7 8 |
|
Some things to note:
- The input (currentState) is a
ICartState
because we don’t know what state it will be. - The return type is also a
ICartState
because we don’t know what the state will be after adding the item. Well, actually we do in this particular case, but generally we won’t. So for exampleRemoveItem
could return either an empty state or an active state. - The input value is not ‘edited in place’ but transformed into a new value. In the state implementations above, you’ll have seen that all the states are immutable. It’s not just a ‘nice-to-have’, but is critical for these kinds of state based transitions.
A particular important point is that all three lambdas return something. In this case, we can call Add
for the empty and active states. But what about the paid state? It doesn’t have an Add
method!
This is where we are forced to make a choice: we could throw an exception or log an error. But in this case, we will ignore it. We have to return a valid cart, so we can’t return null. Instead, we’ll just return the cart that was passed in.
Now earlier I talked about a similar decision for the empty state – should it throw an exception or silently ignore the problem. There is a big difference however between that case and this one. however. In that case, the decision was inside the empty state’s implementation. The decision would be applicable to all callers.
But in this approach, the decision is up to the caller, not the implementor. It is up to the caller to decide what to do, and it can provide different behavior in different circumstances.
Finally, I hope you can now see why the Add
method returned an ICartState
rather than a CartStateActive
. It is because all the the lambdas must return the same type, and in the case of transition from one state to another, that type will always be ICartState
.
Ok, let’s see how Remove
and Pay
would be implemented:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Very similar to the AddProduct
method, except that in these cases, we can only call methods on the active state.
To see how these methods might be combined together, we might have a set of transitions that looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Another example: Email Addresses
We’ll quickly look at another way these states can be used, namely for handling a domain object that might have distinct ‘valid’ and ‘invalid’ states that mustn’t be mixed up.
Consider an email address. It can valid (conforming to the RFC spec) or invalid. We might want to allow both states in our domain, but on the other hand, only allow valid emails to be sent to.
The IEmailAddress template
We’ll call the common state IEmailAddress
. We will use the EmailAddress
state to represent any email address, including invalid ones, and the ValidEmailAddress
state to mean only valid emails.
So here is the template:
1 2 3 4 |
|
This generates the same kind of code as we saw for the shopping cart states:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
Implementing the states
We’ll start by adding a property to the interface that both states will have to implement:
1 2 3 4 |
|
Next, we’ll implement the general email address.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Note that it implements a static New
method that returns either a ValidEmailAddress
or a invalid EmailAddress
.
Finally, the ValidEmailAddress
implementation is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
As you can see, only the ValidEmailAddress
implements the SendMessage
method.
Using the email address
Let’s look at two test cases to see how this works in practice.
Test: If the address does not contain “@”, the address is not valid
1 2 3 4 5 6 7 8 |
|
Test: If the address does contain “@”, the address is valid
Yes, the validation is a bit crude!
1 2 3 4 5 6 7 8 |
|
Test: You cannot send email to an invalid address
1 2 3 4 5 6 7 8 9 10 |
|
Again, note that you cannot bypass the type safety. What would happen if you attempted to send an email to an invalid address like this:
1 2 3 4 5 6 7 8 9 10 |
|
Try it and see!
Summary
Well, I hope you found this interesting and useful. It’s definitely a different way of thinking if you are not familiar with functional programming.
BTW, you might be wondering why the template is called “FoldStates”. The answer is that this technique of having a different function for each case is more generally called a ”fold” (or catamorphism if you want to get fancy).
In this example, we have only just touched on the power of folds. In a language like F# that supports recursive data structures, they are a key part of a functional programmers toolbelt.
Getting the code
You can get the code two ways:
- Download the template only: FoldStates.ttinclude.
- or, from the git repository for this blog (see the about page). This has a full set of examples and unit tests.