Here’s where we got to last time in building a traffic light using Blazor.

TrafficLight.razor.cs

public class TrafficLightBase : ComponentBase
{
    private State _currentState = State.Stop;
    protected TrafficLightState Lights => 
        TrafficLightState.Resolve(_currentState);

    public void Toggle()
    {
        _currentState = _currentState switch
        {
            State.Stop => State.GetReadyToGo,
            State.GetReadyToGo => State.Go,
            State.Go => State.GetReadyToStop,
            State.GetReadyToStop => State.Stop,
            _ => _currentState
        };
    }
}

_currentState refers to an enum.

Every time Toggle is invoked (by clicking a button) _currentState is checked to see what state should come next.

Then, the component re-renders, at which point it will figure out which lights to show by calling TrafficLightState.Resolve, passing in the value of _currentState.

Death to the enum

The enum here feels a little disconnected from our business logic. We’re having to maintain its current value in a field, just so we can figure out:

  • What state we’re currently in
  • What state should come next

But this feels like core business logic, and potentially something which we shouldn’t have to keep track of in addition to the traffic light state object itself.

One way to remove an enum, is to make better use of objects, opening the doors to employ polymorphism to trigger different behaviours.

In this case the question is, if we ditch the enum, how do we know which state we’re looking at? (and which one comes next?)

One way, is to use C#‘s new pattern matching capabilities.

A word on pattern matching with C#

Pattern matching is all about testing the “shape” of a value.

If it’s this shape, do one thing, if it’s that shape, do something else.

As humans we pattern match all the time. When you look at a banana you recognise it’s shape and appearance, and you know how to peel it.

You know it’s not an apple, but if it were an apple, you’d recognise that and be able to apply the correct peeling technique to that too.

In practice, if and switch statements are pattern matching algorithms; the newest versions of C# offer some new tools to make pattern matching easier/more flexible.

Specifically, it’s now possible to both check for the type of something, and get a reference to the object as that type at the same time.

switch(shape){
    case Square s:
        return s.Side * s.Side;
    case Circle c:
        return c.Radius * c.Radius * Math.PI;    
}

(see the official docs for this example in more detail)

Before you would have had to check the type, then cast the object to that type; this approach condenses those two steps into one line in the switch statement.

Also (and we won’t use this here, but it’s useful to know) you can use when clauses to further adjust your logic based on specific aspects of the object in question.

switch(shape){
    case Square s when s.Side == 0;
    case Circle c when c.Radius == 0;
        return 0;

    case Square s when:
        return s.Side * s.Side;
    case Circle c:
        return c.Radius * c.Radius * Math.PI;    
}

Finally, we can combine this with the new switch expression syntax…

    var result = shape switch
    {
        Square s when s.Side == 0 => 0,
        Circle c when c.Radius == 0 => 0,
        Square s => s.Side * s.Side,
        Circle c => c.Radius * c.Radius * Math.PI,
    }

One downside here seems to be that you can’t easily express multiple tests resulting in the same answer. Our switch statement above had tests for c.Radius and s.Side both resulting in the answer being returned as 0.

Alas, in the switch expression we have to write those both out as two separate test/results…

    Square s when s.Side == 0 => 0,
    Circle c when c.Radius == 0 => 0,

A strategy for removing the enum

So with these pattern matching tools available to us, my plan is to represent the different traffic light states as specific types.

We can then pattern match on these types to establish what the new state should be when we attempt to cycle the traffic light to its next state.

First up, we need to create different classes to represent the possible states of our traffic light (doing away with TrafficLightState which we had previously).

public class StopState : ITrafficLightState
{
    public bool RedOn { get; } = true;
    public bool AmberOn { get; } = false;
    public bool GreenOn { get; } = false;
}

public class GetReadyToGoState : ITrafficLightState
{
    public bool RedOn { get; } = true;
    public bool AmberOn { get; } = true;
    public bool GreenOn { get; } = false;
}

public class GoState : ITrafficLightState
{
    public bool RedOn { get; } = false;
    public bool AmberOn { get; } = false;
    public bool GreenOn { get; } = true;
}

public class GetReadyToStopState : ITrafficLightState
{
    public bool RedOn { get; } = false;
    public bool AmberOn { get; } = true;
    public bool GreenOn { get; } = false;
}

Note they all implement the same interface…

public interface ITrafficLightState
{
    bool RedOn { get; }
    bool AmberOn { get; }
    bool GreenOn { get; }
}

Now, we can use pattern matching on the type, to figure out which one we currently have, and which should come next…

TrafficLight.razor.cs

public class TrafficLightBase : ComponentBase
{
    protected ITrafficLightState Lights { get; set; } = new StopState();

    protected void Toggle()
    {
        Lights = Lights switch
        {
            StopState _ => new GetReadyToGoState(),
            GetReadyToGoState _ => new GoState(),
            GoState _ => new GetReadyToStopState(),
            GetReadyToStopState _ => new StopState(),
            _ => throw new ArgumentOutOfRangeException(nameof(Lights))
        };
    }
}

Incidentally, the _ here is a discard variable. It’s a temporary, dummy variable that can’t be used anywhere else in your code. We could have used regular variables here…

StopState s => new GetReadyToGoState(),

But because we’re not actually using that assigned variable anywhere, it’s neater in this case to use the discard pattern. That way, any attempt to retrieve its value or assign a value to it will generate a compiler error. This makes explicit, we don’t care about the value.

Now, our Lights property starts off with an instance of StopState.

When we invoke the Toggle method, we use C# pattern matching (and the new switch expression syntax) to determine the type of the current value of Lights.

For example, if the type is StopState (which it will be when we first render the component) we’ll instantiate an instance of GetReadyToGoState and assign that to Lights.

At this point, because our markup is bound to Lights the UI will be updated accordingly.

What do we gain?

So this works, but do we gain anything, or does it needlessly add complication to the code?

Well, now we’re using objects, we can implement different behaviour for each of our traffic light states by updating the relevant class.

We can also add new states quite easily, by declaring a new implementation of ITrafficLightState and amending our switch expression accordingly.

In that sense, we are adhering to the “Open Closed Principle” in that we can create new classes and drop them in, without having to modify any of the other implementations.

We’re also storing this state in one place Lights, without needing a separate field (our enum value) to figure out what state we’re in.

So overall, it feels like we’re gaining more than we lose.

Check out the complete source code for the refactored traffic light here.

Next up

Making Scott Hanselman’s Powerpoint Greenscreen trick work with RevealJS via Blazor Server
Scott Hanselman did this neat thing with OBS and Powerpoint to put himself “in” his slides. Here’s a version which works with RevealJS via Blazor Server
Edit and Replay your network requests, direct from the browser
Half the battle with building a client-side web app (using Blazor, React etc) is figuring out what calls are being made to the network. Now you can see but also edit those requests and replay them…
Make a responsive Navbar with Blazor and Tailwind?
You know those clever navbar menus which collapse to a “burger” icon on smaller resolutions? Here’s how you can build one using Blazor and Tailwind CSS