State Pattern C#

Contents

What Is State Pattern C#?

State Pattern in C# allow an object to alter its behavior when its internal state changes.

The State pattern, can be seen as a dynamic version of the Strategy pattern. When the state inside an object changes, it can change its behavior by switching to a set of different operations.

This is achieved by an object variable changing its subclass, within a hierarchy.

Below is the UML and sequence diagram of State pattern from Wikipedia.

State pattern

Let’s us see an example to better understand this concept.

State Pattern C# Example

Let’s take a look at an example of Work Item Tracking. Work Item Tracking is simply the bug tracking systems that we have all used such as Team Foundation Server. They all have in common that the work items or bugs go through a transition process. In each step of the transition they change states, and along with the change of state, the behavior of the work item also changes.

Let’s take a look at an implementation of this simple example.

Program.cs

 1 using System;
 2 using System.Linq;
 3 
 4 namespace Example_1
 5 {
 6     class Program
 7     {
 8         static void Main(string[] args)
 9         {
10             var workItemRepository = new WorkItemRepository();
11             WorkItem.Init(workItemRepository);
12             string title = null, desc = null;
13             string command;
14             int id;
15             try
16             {
17                 command = args[0].ToLower();
18                 id = int.Parse(args[1]);
19 
20                 if (args.Count() > 2)
21                 {
22                     title = args[2];
23                     desc = args[3];
24                 }
25             }
26             catch (Exception)
27             {
28                 PrintUsage();
29                 return;
30             }
31 
32             try
33             {
34                 WorkItem workItem;
35                 switch (command)
36                 {
37                     case "create":
38                         workItem = WorkItem.Create(id);
39                         workItem.Edit(title, desc);
40                         workItem.Print();
41                         break;
42 
43                     case "update":
44                         workItem = WorkItem.FindById(id);
45                         workItem.Edit(title, desc);
46                         workItem.Print();
47                         break;
48 
49                     case "open":
50                         workItem = WorkItem.FindById(id);
51                         workItem.Open();
52                         break;
53 
54                     case "resolve":
55                         workItem = WorkItem.FindById(id);
56                         workItem.Resolve();
57                         break;
58 
59                     case "close":
60                         workItem = WorkItem.FindById(id);
61                         workItem.Close();
62                         break;
63 
64                     case "delete":
65                         workItem = WorkItem.FindById(id);
66                         workItem.Delete();
67                         break;
68 
69                     case "print":
70                         workItem = WorkItem.FindById(id);
71                         workItem.Print();
72                         break;
73                 }
74 
75                 workItemRepository.Save();
76             }
77             catch (Exception)
78             {
79                 PrintUsage();
80             }
81         }
82 
83         private static void PrintUsage()
84         {
85             Console.WriteLine("create   [id] [title] [description]");
86             Console.WriteLine("update   [id] [title] [description]");
87             Console.WriteLine("resolve  [id]");
88             Console.WriteLine("close    [id]");
89             Console.WriteLine("delete   [id]");
90             Console.WriteLine("print    [id]");
91         }
92     }
93 }

WorkItem.cs

  1 using System;
  2 
  3 namespace Example_1
  4 {
  5     public enum StateEnum { Proposed, Active, Resolved, Closed }
  6 
  7     public class WorkItem
  8     {
  9         public static WorkItemRepository WorkItemRepository { get; set; }
 10 
 11         public static void Init(WorkItemRepository workItemRepository)
 12         {
 13             WorkItemRepository = workItemRepository;
 14         }
 15 
 16         public int Id { get; set; }
 17         public string Title { get; set; }
 18         public string Description { get; set; }
 19         public StateEnum State { get; set; }
 20 
 21         public static WorkItem Create(int id)
 22         {
 23             var workItem = new WorkItem() 
 24             { 
 25                 Id = id, 
 26                 State = StateEnum.Proposed 
 27             };
 28             WorkItemRepository.Add(workItem);
 29             return workItem;
 30         }
 31 
 32         public static WorkItem FindById(int id)
 33         {
 34             return WorkItemRepository.FindById(id);
 35         }
 36 
 37         public void Edit(string title, string description)
 38         {
 39             Title = title;
 40             Description = description;
 41         }
 42 
 43         public void Open()
 44         {
 45             switch (State)
 46             {
 47                 case StateEnum.Proposed:
 48                     this.State = StateEnum.Active;
 49                     break;
 50                 case StateEnum.Active:
 51                     Console.WriteLine("Work Item is already active.");
 52                     break;
 53                 case StateEnum.Resolved:
 54                     Console.WriteLine("Work Item is already resolved.");
 55                     break;
 56                 case StateEnum.Closed:
 57                     Console.WriteLine("Work Item is closed and cannot be modified.");
 58                     break;
 59             }
 60         }
 61 
 62         public void Resolve()
 63         {
 64             switch (State)
 65             {
 66                 case StateEnum.Proposed:
 67                     Console.WriteLine("Work Item is in proposed state and cannot be directly resolved.");
 68                     break;
 69                 case StateEnum.Active:
 70                     this.State = StateEnum.Resolved;
 71                     break;
 72                 case StateEnum.Resolved:
 73                     Console.WriteLine("Work Item is already resolved.");
 74                     break;
 75                 case StateEnum.Closed:
 76                     Console.WriteLine("Work Item is closed and cannot be modified.");
 77                     break;
 78             }
 79         }
 80 
 81         public void Delete()
 82         {
 83             switch (State)
 84             {
 85                 case StateEnum.Proposed:
 86                     WorkItemRepository.Delete(this);
 87                     break;
 88                 case StateEnum.Active:
 89                     Console.WriteLine("Work Item is already active and cannot be deleted.");
 90                     break;
 91                 case StateEnum.Resolved:
 92                     Console.WriteLine("Work Item is already resolved and cannot be deleted.");
 93                     break;
 94                 case StateEnum.Closed:
 95                     WorkItemRepository.Delete(this);
 96                     break;
 97             }
 98         }
 99 
100         public void Close()
101         {
102             switch (State)
103             {
104                 case StateEnum.Proposed:
105                     Console.WriteLine("Work Item is in proposed state and cannot be closed.");
106                     break;
107                 case StateEnum.Active:
108                     Console.WriteLine("Work Item is in active state and cannot be closed.");
109                     break;
110                 case StateEnum.Resolved:
111                     this.State = StateEnum.Closed;
112                     break;
113                 case StateEnum.Closed:
114                     Console.WriteLine("Work Item is already closed.");
115                     break;
116             }
117         }
118 
119         public void Print()
120         {
121             Console.WriteLine($"Id: {Id}");
122             Console.WriteLine($"Title: {Title}");
123             Console.WriteLine($"State: {State}");
124             Console.WriteLine($"Description: {Description}");
125 
126         }
127     }
128 }

WorkItemRepository.cs

 1 using Newtonsoft.Json;
 2 using System.Collections.Generic;
 3 using System.IO;
 4 using System.Linq;
 5 
 6 namespace Example_1
 7 {
 8     public class WorkItemRepository
 9     {
10         private readonly ICollection<WorkItem> workItems;
11         private readonly string filename = "workitems.json";
12 
13         public WorkItemRepository()
14         {
15             workItems = LoadData();
16             if (workItems == null)
17             {
18                 workItems = new List<WorkItem>();
19                 SaveData();
20             }
21         }
22 
23         
24 
25         public void Add(WorkItem workItem)
26         {
27             workItems.Add(workItem);
28             SaveData();
29         }
30 
31         public void Update(WorkItem workItem)
32         {
33             SaveData();
34         }
35 
36         public bool Delete(WorkItem workItem)
37         {
38             var @return = workItems.Remove(workItem);
39             SaveData();
40             return @return;
41         }
42 
43         public long Count()
44         {
45             return workItems.Count;
46         }
47 
48         public WorkItem FindById(int id)
49         {
50             return workItems.FirstOrDefault(w=>w.Id == id);
51         }
52 
53         public void Save()
54         {
55             SaveData();
56         }
57 
58         private void SaveData()
59         {
60             var data = JsonConvert.SerializeObject(workItems);
61             var filePath = Path.Combine(Directory.GetCurrentDirectory(), filename);
62             File.WriteAllText(filePath, data);
63         }
64 
65         private ICollection<WorkItem> LoadData()
66         {
67             var filePath = Path.Combine(Directory.GetCurrentDirectory(), filename);
68             if (!File.Exists(filePath)) return null;
69             var jsonData = File.ReadAllText(filePath);
70             return JsonConvert.DeserializeObject<List<WorkItem>>(jsonData);
71         }
72     }
73 }

I have here our command line interface of a work item tracking system that is using the basic CMMI approach that TFS uses with four states to the work items. There’s Proposed, Active, Resolved, and Closed.

On a work item, you typically can create a work item, edit the title and the description. We can open the work item, which essentially moves it from Proposed to Active, we resolve it, moving it from Active to Resolved, we close it, moving it from Resolved to Closed, and we also can print details about it.

State Pattern Example Console Program

As we can see, there are some issues with the Simple or Naive approach.

  • No extensibility, all of the states are hard-coded in the program. To add a new state, we must go to each method and add a new case to all of the switches.

  • Mixed Concerns, all of the actions for a particular state are spread throughout the code. A change in one action for one state may have an effect on the other states, which means we need to test it, but these are difficult to unit test.

  • Each method has a large number of permutations for the inputs and the corresponding outputs. This leads to a lack of testing and possibly complicated unit tests. All of these issues lead to a maintenance problem.

State pattern can help us to solve all these issues.

So what is the intent of the State Pattern? Well the intent is to change the behavior of the object with each change in the state. As I change the state of the object, I expect all of the actions that go with that state to also change.

I want to, though, encapsulate that logic of each state into a single object, something that is in one area that I can test by itself.

In the current application in the simple or naive approach, we had all of the states in the code, but the State Pattern can allow for the dynamic discovery of new states and the program should be able to integrate them into the process.

And, of course, with all of this we want to make unit testing easier.

Let’s take a look at the implementation of the State Pattern.

In the Main Program, the structure will be pretty much the same. The program starts by creating a repository, parsing the command lines, and then executing the commands on the WorkItems.

However,

The WorkItem has been modified. On the WorkItem I’ve implemented the ICommands interface and I’ve added two new instance variables, one for the State and one for the command. The WorkItem now implements each method of the ICommand interface, and each method in the ICommand interface is implemented by shuttling the method call to the command object’s methods.

ICommand.cs

 1 namespace Example_2
 2 {
 3     public interface ICommand
 4     {
 5         void Open();
 6         void Resolve();
 7         void Close();
 8         bool Delete();
 9         void Print();
10     }
11 }

WorkItem.cs

 1 using Example_2.States;
 2 
 3 namespace Example_2
 4 {
 5     public enum StateEnum { Proposed, Active, Resolved, Closed }
 6 
 7     public class WorkItem : ICommand
 8     {
 9         public static WorkItemRepository WorkItemRepository { get; set; }
10 
11         public static void Init(WorkItemRepository workItemRepository)
12         {
13             WorkItemRepository = workItemRepository;
14         }
15 
16         public int Id { get; set; }
17         public string Title { get; set; }
18         public string Description { get; set; }
19         public StateEnum State 
20         { 
21             get => state; 
22             set
23             {
24                 state = value;
25                 switch (state)
26                 {
27                     case StateEnum.Proposed:
28                         command = new Proposed(this);
29                         break;
30                     case StateEnum.Active:
31                         command = new Active(this);
32                         break;
33                     case StateEnum.Resolved:
34                         command = new Resolved(this);
35                         break;
36                     case StateEnum.Closed:
37                         command = new Closed(this);
38                         break;
39                 }
40             }
41         }
42 
43         private ICommand command;
44         private StateEnum state;
45 
46         public static WorkItem Create(int id)
47         {
48             var workItem = new WorkItem()
49             {
50                 Id = id,
51                 State = StateEnum.Proposed
52             };
53             WorkItemRepository.Add(workItem);
54             return workItem;
55         }
56 
57         public static WorkItem FindById(int id)
58         {
59             return WorkItemRepository.FindById(id);
60         }
61 
62         public void Edit(string title, string description)
63         {
64             Title = title;
65             Description = description;
66         }
67 
68         public void Open()
69         {
70             command.Open();
71         }
72 
73         public void Resolve()
74         {
75             command.Resolve();
76         }
77 
78         public void Close()
79         {
80             command.Close();
81         }
82 
83         public bool Delete()
84         {
85             if (command.Delete())
86             {
87                 WorkItemRepository.Delete(this);
88                 return true;
89             }
90 
91             return false;
92         }        
93 
94         public void Print()
95         {
96             command.Print();
97         }
98     }
99 }

So all of this is pretty simple, but when the state changes, the command object that provides the actual work also needs to change. This is done by implementing the state property. So in the state property, we see here that the value of the state being set is stored in the instance variable, and when the state changes, the state properly will also instantiate a new command object based on the state value and passes in a reference to the WorkItem.

As noted in the code, this does not strictly separate the state from the WorkItems, but you can see here how that functionality can quickly be added so that the WorkItem itself may not know anything about the states in the future.

What you do notice is that apart from the state change, there is no longer any case statements in each method of the WorkItem.

The states themselves have been broken up into a separate class for each state.

Proposed.cs

 1 using System;
 2 
 3 namespace Example_2.States
 4 {
 5     public class Proposed : BaseState, ICommand
 6     {
 7         private readonly WorkItem owner;
 8 
 9         public Proposed(WorkItem owner) : base(owner)
10         {
11             this.owner = owner;
12         }
13 
14         public void Open()
15         {
16             owner.State = StateEnum.Active;
17         }
18 
19         public void Resolve()
20         {
21             Console.WriteLine("Work Item is in proposed state and cannot be directly resolved.");
22         }
23 
24         public void Close()
25         {
26             Console.WriteLine("Work Item is in proposed state and cannot be closed.");
27         }
28 
29         public bool Delete()
30         {
31             return true;
32         }
33     }
34 }

Active.cs

 1 using System;
 2 
 3 namespace Example_2.States
 4 {
 5     public class Active : BaseState, ICommand
 6     {
 7         private readonly WorkItem owner;
 8 
 9         public Active(WorkItem owner) : base(owner)
10         {
11             this.owner = owner;
12         }
13 
14         public void Open()
15         {
16             Console.WriteLine("Work Item is already active.");
17         }
18 
19         public void Resolve()
20         {
21             owner.State = StateEnum.Resolved;
22         }
23 
24         public void Close()
25         {
26             Console.WriteLine("Work Item is in active state and cannot be closed.");
27         }
28 
29         public bool Delete()
30         {
31             Console.WriteLine("Work Item is already active and cannot be deleted.");
32             return false;
33         }
34     }
35 }

Resolved.cs

 1 using System;
 2 
 3 namespace Example_2.States
 4 {
 5     public class Resolved : BaseState, ICommand
 6     {
 7         private readonly WorkItem owner;
 8 
 9         public Resolved(WorkItem owner) : base(owner)
10         {
11             this.owner = owner;
12         }
13 
14         public void Open()
15         {
16             Console.WriteLine("Work Item is already resolved.");
17         }
18 
19         public void Resolve()
20         {
21             Console.WriteLine("Work Item is already resolved.");
22         }
23 
24         public void Close()
25         {
26             owner.State = StateEnum.Closed;
27         }
28 
29         public bool Delete()
30         {
31             Console.WriteLine("Work Item is already resolved and cannot be deleted.");
32             return false;
33         }
34     }
35 }

Closed.cs

 1 using System;
 2 
 3 namespace Example_2.States
 4 {
 5     public class Closed : BaseState, ICommand
 6     {
 7         private readonly WorkItem owner;
 8 
 9         public Closed(WorkItem owner) : base(owner)
10         {
11             this.owner = owner;
12         }
13 
14         public void Open()
15         {
16             Console.WriteLine("Work Item is closed and cannot be modified.");
17         }
18 
19         public void Resolve()
20         {
21             Console.WriteLine("Work Item is closed and cannot be modified.");
22         }
23 
24         public void Close()
25         {
26             Console.WriteLine("Work Item is already closed.");
27         }
28 
29         public bool Delete()
30         {
31             return true;
32         }
33     }
34 }

BaseState.cs

 1 using System;
 2 
 3 namespace Example_2.States
 4 {
 5     public abstract class BaseState
 6     {
 7         private WorkItem owner;
 8         public BaseState(WorkItem owner)
 9         {
10             this.owner = owner;
11         }
12 
13         public void Print()
14         {
15             Console.WriteLine($"Id: {owner.Id}");
16             Console.WriteLine($"Title: {owner.Title}");
17             Console.WriteLine($"State: {owner.State}");
18             Console.WriteLine($"Description: {owner.Description}");
19         }
20     }
21 }

If we look at the active state, we see how the Active class is inherited from the BaseState and implements the ICommands interface.

The inheritance from a BaseState is not required, but it shows that we can still leverage commonality in the states as long as they all implement the ICommand’s interface so they can have a relationship back with the WorkItem.

Notice that in the construction of the Active class, the reference to the WorkItem is captured as the owner. This allows the states themselves to pass information back to the WorkItem or use the WorkItem in one of the method calls. We see this in the execution of the Print method.

A difficult method to implement is the Delete method. In this case, the Delete method of a particular state needs to determine if the WorkItem can be deleted, and send that information back to the WorkItem. The reason is, this WorkItem is part of a collection, it doesn’t really know how it’s being stored. So in this case, I would go back to the original program.

Benefit Of The State Pattern

The benefit of the State Pattern should be obvious by now. One of the immediate benefits is the Separation of Concerns.

Whereas the WorkItem used to contain all of the logic about creation, the data, the state, and all of the method behaviors, it is now dedicated to simply the creation of the WorkItems and the data they contain.

The method behaviors have been abstracted away and are localized to the specific state of the object.

The state objects themselves can be reused. This is where other design patterns such as the Flyweight Pattern can be used so that only a few instantiations of the state objects are created at any time.

Often, the state objects will only have a single representation implemented by the Singleton Pattern and will be reused throughout the application.

All of this provides a way to simplify the program. This allows for a clearer understanding and easier testing, which all leads to greater maintainability.

Where To Apply State Pattern?

  • When an object’s behavior depends on its state, and it must change its behavior at run-time depending on that state.

  • When object contains large number of operations with multipart conditional statements that depend on the object’s state. This state is usually represented by one or more enumerated constants.

Note: You can download the complete solution demo from my github repository.

Further Reading

  • Strategy Pattern C# - Strategy pattern is one of the most useful design patterns in OOP. It lets you select an algoritm’s implementation at runtime. In this article, you’ll learn how to implement the Strategy pattern in C#.

  • How to Create a Simple Circuit Breaker in C# by Patrick Desjardins - Circuit Breaker pattern is named from house circuit breaker — something fail, it opens the circuit, thus does not do any damage. With code, the circuit breaker pattern is useful when we access a resource that can slow down the system. In this article Patrick demonstrates how to create a simple Circuit Breaker in C#. The interesting thing is Circuit Breaker pattern implements State pattern to handle Circuit Breaker states.

  • Stateless 3.0 - A State Machine library for .NET Core by Scott Hanselman- In this post, Scott introduces “Stateless” which is a simple library for creating state machines in C# code. You can use state machines for anything. You can certainly describe high-level business state machines, but you can also easily model IoT device state, user interfaces, and more.

  • State by Bob Nystrom - This is a chapter from Bob’s book ‘Game Programming Patterns’ that explains the State pattern from a game developer perspective.

State Pattern C#
Share this

Subscribe to Code with Shadman