Meta Description: Learn how to implement a simple state machine in C# using a switch statement without relying on external libraries. This article walks through a combination lock example, demonstrating how to manage states and transitions in a readable and efficient way, ideal for small applications.
Introduction
State machines can often be overly complex, especially when using centralized structures like dictionaries to manage transitions. In this article, we’ll take a simpler approach to state machines using a switch
statement to handle states and transitions. We’ll demonstrate this approach by modeling a combination lock with three states: Locked, Failed, and Unlocked.
Why Simplify the State Machine?
In many cases, defining a state machine with a dictionary of transitions adds unnecessary complexity. Instead, using a switch
statement allows us to manage state transitions inline, making the code more readable and straightforward. With this approach, we avoid the need for a centralized data structure to manage states, making the logic easier to follow, especially for simple state machines.
Example Scenario: Combination Lock
In this example, a combination lock will have the following states and behaviors:
- Locked: The initial state where the lock is locked.
- Failed: The state when an incorrect code is entered.
- Unlocked: The state when the correct code is entered, unlocking the lock.
Implementation Steps
Let’s walk through the steps to create this combination lock using a switch
statement in C#.
Step 1: Define States and Variables
We’ll start by defining the states using an enum. Then, we’ll set up a few initial variables:
- Correct Code: The combination that unlocks the lock.
-
Initial State: Set to
Locked
. - Entry Builder: Stores the user’s input as they attempt to unlock the combination.
using System;
using System.Text;
namespace SimpleStateMachineExample
{
// Define the states of the lock
public enum State
{
Locked,
Failed,
Unlocked
}
class CombinationLock
{
private readonly string _correctCode = "1234";
private State _currentState = State.Locked;
private StringBuilder _entry = new StringBuilder();
public void Run()
{
while (true)
{
switch (_currentState)
{
case State.Locked:
HandleLockedState();
break;
case State.Failed:
HandleFailedState();
break;
case State.Unlocked:
HandleUnlockedState();
return; // End program after unlocking
}
}
}
}
}
Step 2: Implement Each State’s Logic
Each state requires different logic. We’ll implement separate methods to handle the behavior of each state: Locked, Failed, and Unlocked.
Locked State: In this state, we wait for the user to input digits for the combination. If the entry matches the correct code, we transition to the Unlocked state. If a digit is incorrect, we switch to the Failed state.
Failed State: If the user enters the wrong code, this state resets the
entry
and allows the user to try again by returning to the Locked state.Unlocked State: Displays an "Unlocked" message and exits the program, as the lock is now open.
class CombinationLock
{
// Handles the Locked state behavior
private void HandleLockedState()
{
Console.Write("Enter a digit: ");
char input = Console.ReadKey().KeyChar;
_entry.Append(input);
// Check if entry matches the correct code so far
if (_correctCode.StartsWith(_entry.ToString()))
{
if (_entry.ToString() == _correctCode)
{
_currentState = State.Unlocked;
}
}
else
{
_currentState = State.Failed;
}
Console.WriteLine();
}
// Handles the Failed state behavior
private void HandleFailedState()
{
Console.CursorLeft = 0;
Console.WriteLine("Failed");
_entry.Clear();
_currentState = State.Locked;
}
// Handles the Unlocked state behavior
private void HandleUnlockedState()
{
Console.CursorLeft = 0;
Console.WriteLine("Unlocked");
}
}
Step 3: Run the State Machine
In the Main
method, we initialize the CombinationLock
class and start the state machine by calling the Run
method.
class Program
{
static void Main(string[] args)
{
var combinationLock = new CombinationLock();
combinationLock.Run();
}
}
Explanation of the Code
Switch Statement: The
Run
method uses aswitch
statement to manage transitions between states, calling the appropriate method based on the current state.HandleLockedState: This method reads each digit as the user enters it, adding it to the
entry
. If the input matches the correct code, it transitions to the Unlocked state; otherwise, it goes to the Failed state.HandleFailedState: Clears the
entry
, allowing the user to start over from the Locked state.HandleUnlockedState: Prints "Unlocked" to indicate success and exits the program.
Example Walkthrough
When you run the code, you can try different inputs to see how the lock behaves:
- Correct Code: Enter "1234" in sequence, and the lock displays "Unlocked."
- Incorrect Code: Enter any wrong digit, and the lock will display "Failed" and reset, allowing you to try again.
Benefits of Using a Switch-based State Machine
This approach has several advantages:
- Simplicity: The logic is straightforward and easy to follow, as each state’s behavior is managed within a single method.
-
Readability: The
switch
statement makes it clear what actions are available in each state. - Flexibility: Small state machines like this don’t require complex libraries, making them easier to modify or extend.
Conclusion
Using a switch
statement for simple state machines can be a great alternative to centralized dictionaries or libraries. This approach keeps the code easy to read and maintain while offering the flexibility to handle small, finite state systems efficiently. For complex applications, libraries may offer additional features, but this approach is ideal for smaller state machines.
This example shows how you can model state transitions naturally without needing a formalized structure, providing a lightweight solution for simple state-based logic in C#.