Changing State with the Switch Expression (C# 8)

A previous blog article on moving from the switch statement to the C# 8 switch expression led to some comments from readers that preferred the old switch statement in all cases. That’s why I had to create another article to show more examples of the switch expression to show more advantages (in my opinion) of the new switch expression. The samples also make use of C# 7 tuples and C# 8 pattern matching with a simple console application and an UWP application to have a visible traffic light.

Traffic lights

Traffic Light Scenario

Let’s start with a simple sample using a switch expression in its simplest form changing one state to another, and a UWP application to visualize traffic light switching between red, yellow, and green.

With the first sample, a simple enum type defines these states:

public enum LightState
{
  Red,
  Yellow,
  Green
};

Now it’s easy to switch between these colors using the switch expression:

public LightState GetNextLight(LightState currentLight)
  => currentLight switch
  {
       LightState.Red => LightState.Yellow,
    LightState.Yellow => LightState.Green,
     LightState.Green => LightState.Red,
                    _ => throw new InvalidOperationException()
  };

Calling the method GetNextLight, the light switches from red to yellow, from yellow to green, and from green to red.

This implementation of the switch expression is simple, but it’s too simple for this scenario. A green light should not directly switch to red, but to yellow instead. The next state after yellow should either be green or red, depending on the previous state. This is where tuples come into play in the next step. But before switching to tuples, let’s create the user interface of the UWP application.

The UWP application displays a Canvas element containing three Ellipse elements for the three lights of the traffic light. They all bind to the same property LightState with the converter TrafficLightConverter to convert the LightState to a brush.

<Canvas Background="DarkGray" Width="400" Height="900">
  <Ellipse Canvas.Left="90" Canvas.Top="40" Width="220" Height="220"
    Fill="{x:Bind LightState,
      Mode=OneWay,
      Converter={StaticResource TrafficLightConverter},
      ConverterParameter='Red'}" />
  <Ellipse Canvas.Left="90" Canvas.Top="280" Width="220" Height="220"
    Fill="{x:Bind LightState,
      Mode=OneWay,
      Converter={StaticResource TrafficLightConverter},
      ConverterParameter='Yellow'}" />
  <Ellipse Canvas.Left="90" Canvas.Top="520" Width="220" Height="220"
    Fill="{x:Bind LightState,
      Mode=OneWay,
      Converter={StaticResource TrafficLightConverter},
      ConverterParameter='Green'}" />
</Canvas>

The converter class TrafficLightToBrushConverter converts the enum value of the LightState to a brush. This implementation makes use of the switch expression as well to return the selected or a black brush based on the parameter received. In the previous code the Fill property of the Ellipse elements bind to the same property LightState using the same TrafficLightToBrushConverter passing different parameters – Red, Yellow, and Green. In case the parameter has a value of “Red”, either the _redBrush or the _blackBrush is returned depending on the value of the lightState variable:

public class TrafficLightToBrushConverter : IValueConverter
{
  private readonly Brush _redBrush = new SolidColorBrush(Colors.Red);
  private readonly Brush _yellowBrush = new SolidColorBrush(Colors.Yellow);
  private readonly Brush _greenBrush = new SolidColorBrush(Colors.Green);
  private readonly Brush _blackBrush = new SolidColorBrush(Colors.Black);

  public object Convert(object value, Type targetType, object parameter, string language)
  {
    if (value is LightState lightState)
    {
      return parameter switch
      {
           "Red" => lightState == LightState.Red ? _redBrush : _blackBrush,
        "Yellow" => lightState == LightState.Yellow ? _yellowBrush : _blackBrush,
         "Green" => lightState == LightState.Green ? _greenBrush : _blackBrush,
               _ => throw new ArgumentException($"{parameter} is an invalid value for {nameof(parameter)}")
      };
    }
    return null;
  }

  public object ConvertBack(object value, Type targetType, object parameter, string language)
    => throw new InvalidOperationException();
}

The code-behind file of the Main page defines a dependency property LightState that is bound in the XAML file, and starts a DispatcherTimer to get the new traffic light and update the user interface every three seconds.

public sealed partial class MainPage : Page
{
  public MainPage()
  {
    this.InitializeComponent();
    LightState = LightState.Red;

    var switcher = new TrafficLightStateSwitcher();
    var timer = new DispatcherTimer();
    timer.Interval = TimeSpan.FromSeconds(3);
    timer.Tick += (sender, e) =>
    {
      LightState = switcher.GetNextLight(LightState);
    };
    timer.Start();
  }

  public LightState LightState
  {
    get => (LightState)GetValue(LightStateProperty);
    set => SetValue(LightStateProperty, value);
  }

  public static readonly DependencyProperty LightStateProperty =
    DependencyProperty.Register("LightState", typeof(LightState), typeof(MainPage), new PropertyMetadata(null));
}

In the source code download, the C# code is not exactly as shown here, as you can already see the switch expression making use of tuples as you’ll see next here in the console application that follows.

With the user interface you can see the dynamic change of traffic light.

UWP application with traffic lights

Switch Expression using Tuples

For the traffic light scenario, the previous switch expression was too simple. Changing the light from yellow depends on the previous state of the light. Traffic light switches with these states: red – yellow – green – yellow – red. This will be fixed next. But here, this is not the only change. In Austria and some other countries the state is more complex to add flashing green light: red – yellow – green – flashing green – yellow – red. To not keep it too simple, I’m using this state as well including a blink count. During night, and if the traffic light is in an erroneous state, it can flash yellow.

This is the new enum that is defined for the states in the console app sample:

public enum LightState
{
  Undefined,
  Red,
  Yellow,
  FlashingGreen,
  Green,
  FlashingYellow
};

The new implementation of the GetNextLight method receives two parameters – the current state of the light, and the previous state of the light – because of the previously mentioned requirement for the yellow light. The method returns a tuple with the new current and previous state. In the implementation, you can define matches on a tuple, and return a tuple. In case the currentLight is yellow, and the previousLight is red, the new state green is returned as the current light. In case the currentLight is yellow, and the previousLight is flashing green, the new current light is red.

public (LightState Current, LightState Previous) GetNextLight1(LightState currentLight, LightState previousLight)
{
  return (currentLight, previousLight) switch
  {
    (LightState.FlashingYellow, LightState.Undefined) => (LightState.Red, currentLight),
          (LightState.FlashingYellow, LightState.Red) => (LightState.Red, currentLight),
        (LightState.FlashingYellow, LightState.Green) => (LightState.Red, currentLight),
       (LightState.FlashingYellow, LightState.Yellow) => (LightState.Red, currentLight),
          (LightState.Red, LightState.FlashingYellow) => (LightState.Yellow, currentLight),
                  (LightState.Red, LightState.Yellow) => (LightState.Yellow, currentLight),
                  (LightState.Yellow, LightState.Red) => (LightState.Green, currentLight),
           (LightState.Yellow, LightState.GreenBlink) => (LightState.Red, currentLight),
                (LightState.Green, LightState.Yellow) => (LightState.FlashingGreen, currentLight),
         (LightState.FlashingGreen, LightState.Green) => (LightState.Yellow, currentLight),
                                                    _ => (LightState.FlashingYellow, currentLight)
  };
}

The implementation of the switch expression is easy – based on two values, two values are returned with the help of tuples. However, not all needed cases are covered; and several cases have the same implementation. Using a switch statement instead of the switch expression, you can just fall thru from one case to the other. This is not possible with the switch expression. However, you can ignore values as shown next.

Using the discard Pattern

The new implementation of the GetNextLight method makes use of the discard pattern which allows simplification of the implementation. In case the current state of the light is red, always yellow is returned; no matter what the previous light state was. Just with the current state yellow, the previous state is used to differentiate to either return green or red. With the discard pattern an underscore _ is used to ignore this value:

public (LightState Current, LightState Previous) GetNextLight2(LightState currentLight, LightState previousLight)
  => (currentLight, previousLight) switch
  {
                (LightState.FlashingYellow, _) => (LightState.Red, currentLight),
                           (LightState.Red, _) => (LightState.Yellow, currentLight),
           (LightState.Yellow, LightState.Red) => (LightState.Green, currentLight),
                         (LightState.Green, _) => (LightState.FlashingGreen, currentLight),
                 (LightState.FlashingGreen, _) => (LightState.Yellow, currentLight),
    (LightState.Yellow, LightState.GreenBlink) => (LightState.Red, currentLight),
                                             _ => (LightState.FlashingYellow, currentLight)
  };

Enhancing tuples with a third value

To define a number of flashes – e.g. the traffic light needs to flash green for three times before it moves to the next state – the tuple needs to be enhanced by another value. A tuple can consist of 2, 3, 4, 5… values of different types. Let’s add the count to the tuple in the next implementation of the GetNextLight method. Here, a match is defined based on the count with the flashing green option. In case the count is any value other than two, the count is incremented, and the light stays flashing green. In case the count has a value of two, the light switches to yellow, and the count is reset to 0.

public (LightState Current, LightState Previous, int count) GetNextLight3(
  LightState currentLight,
  LightState previousLight,
  int currentCount = 0)
  => (currentLight, previousLight, currentCount) switch
  {
                  (LightState.FlashingYellow, _, _) => (LightState.Red, currentLight, 0),
                             (LightState.Red, _, _) => (LightState.Yellow, currentLight, 0),
             (LightState.Yellow, LightState.Red, _) => (LightState.Green, currentLight, 0),
                           (LightState.Green, _, _) => (LightState.FlashingGreen, currentLight, 0),
                   (LightState.FlashingGreen, _, 2) => (LightState.Yellow, currentLight, 0),
                   (LightState.FlashingGreen, _, _) => (LightState.FlashingGreen, currentLight, ++currentCount),
   (LightState.Yellow, LightState.FlashingGreen, _) => (LightState.Red, currentLight, 0),
                                                  _ => (LightState.FlashingYellow, currentLight, 0)
  };

Using a Type and Pattern Matching

Using tuples can be a matter of taste – you can create custom classes or structs as well. Having a selection on the current and the previous state, I think the tuple is a great option. Having more different values for the options, the tuples can become too complex to read, and a custom type can be the better way for an implementation. In the following sample I’m not only using the count for the flashes but also another value: the number of milliseconds a light should stay on. The struct LightStatus defines read-only properties and constructors to initialize the struct. With the implementation of the constructors I’m using the tuple syntax to initialize multiple properties within an lambda expression. Curly brackets are not needed in that case. Behind the scenes, the compiler does not create a new tuple but instead directly assigns the passed values to the members of the type:

public struct LightStatus
{
  public LightStatus(LightState current, LightState previous, int seconds, int flashCount)
    => (Current, Previous, Milliseconds, FlashCount) = (current, previous, seconds, flashCount);

  public LightStatus(LightState current, LightState previous, int seconds) : this(current, previous, seconds, 0) { }
  public LightStatus(LightState current, LightState previous) : this(current, previous, 3) { }

  public LightState Current { get; }
  public LightState Previous { get; }
  public int FlashCount { get; }
  public int Milliseconds { get; }
}

The next implementation of the GetNextLight method receives a LightStatus parameter and returns one. Using pattern matching based on the values of properties of the LightStatus type, new instances of the stack-based LightStatus struct are returned. Instead of using the discard pattern, just the properties needed for comparison are used – like shown with LightState.Yellow with the previous state, and FlashingGreen with the FlashCount:

public LightStatus GetNextLight4(LightStatus lightStatus)
  => lightStatus switch
  {
    { Current: LightState.FlashingYellow } => new LightStatus(LightState.Red, LightState.FlashingYellow, 5000),
    { Current: LightState.Red } => new LightStatus(LightState.Yellow, lightStatus.Current, 3000),
    { Current: LightState.Yellow, Previous: LightState.Red } => new LightStatus(LightState.Green, lightStatus.Current, 5000),
    { Current: LightState.Green } => new LightStatus(LightState.FlashingGreen, lightStatus.Current, 1000),
    { Current: LightState.FlashingGreen, FlashCount: 2 } => new LightStatus(LightState.Yellow, lightStatus.Current, 2000),
    { Current: LightState.FlashingGreen } => new LightStatus(LightState.FlashingGreen, lightStatus.Current, 1000,   
                                               lightStatus.FlashCount + 1),
    { Current: LightState.Yellow, Previous: LightState.FlashingGreen } => new LightStatus(LightState.Red, lightStatus.Current, 5000),
    _ => new LightStatus(LightState.FlashingYellow, lightStatus.Current, 1000)
  };

With the console sample application, the class TrafficLightRunner invokes creates endless loops to invoke the different implementations:

public class TrafficLightRunner
{
  private readonly TrafficLightSwitcher _switcher = new TrafficLightSwitcher();

  public async Task UseTuplesAsync()
  {
    LightState current = LightState.FlashingYellow;
    LightState previous = LightState.Undefined;
    while (true)
    {
      (current, previous) = _switcher.GetNextLight2(current, previous);
      Console.WriteLine($"new light: {current}, previous: {previous}");
      await Task.Delay(2000);
    }
  }
  //...

Taking command line input from the user interface, I’ve used an experimental library: System.Commandline.Experimental. With this, user input is converted to an enum, and a switch statement in the command handler decides the method of the runner that should be invoked. This code makes use of the old switch statement instead of the new switch expression because here not a value is returned, but instead an action is invoked.

public enum AppMode
{
  Tuple,
  TupleCount,
  Type
}

class Program
{
  static void Main(string[] args)
  {
    var rootCommand = new RootCommand("Sample showing switch expressions")
    {
      new Option("--mode", "select the mode to run the application")
      {
        Argument = new Argument<AppMode>(defaultValue: () => AppMode.Tuple)
      },
    };

    rootCommand.Handler = CommandHandler.Create<AppMode>(async (mode) =>
    {
      var runner = new TrafficLightRunner();

      switch (mode)
      {
        case AppMode.Tuple:
          await runner.UseTuplesAsync();
          break;
        case AppMode.TupleCount:
          await runner.UseTuplesWithCountAsync();
          break;
        case AppMode.Type:
          await runner.UseCustomTypeAsync();
          break;
      }
    });

    rootCommand.Invoke(args);
  }
}

You can run the application passing different command line options, or debug it from Visual Studio configuring the command line options in the Debug settings.

> dotnet run -- --help
> dotnet run -- --mode Tuple
> dotnet run -- --mode TupleCount
> dotnet run -- --mode Type

Take away

The new C# 8 switch expression can simplify code. In some scenarios and with the help of lambdas, tuples, and pattern matching, I see advantages on using this new expression. Of course there are also scenarios where the switch statement is better suited. The switch statement is not listed to become obsolete.

What do you think? I’m interested in your opinion on the C# 8 switch expression compared to the switch statement. Did this article changed your mind? Do you still prefer the switch statement in all cases? What are your thoughts on the new C# 8 features?

In case you want to enhance the UWP application with the features from the console sample app, or other features such as animating the light, you’re free to create a pull request for this sample.

If you’ve read this far, consider buying me a coffee which helps me staying up longer and writing more articles.

Buy Me A Coffee

You can get the complete console app and UWP app samples.

Enjoy learning and programming!

Christian

Links

Flashing Green Light

Moving from the switch statement to the switch expression

Experimental Command Line API

Explicit Interface Implementation with C# 8

Async Streams with C# 8

C# 8 & No More NullReferenceExceptions – What about legacy code?

Using, using, using with C# 8

C# 8 Pattern Matching Extended

C# 8 Indexes and Ranges

C# 7 Pattern Matching

More information on C# and programming .NET Core applications is in my book Professional C# 7 and .NET Core 2.0, and in my workshops.

Traffic Light image ID 124846656 © Monticelllo | Dreamstime.com

Advertisements

3 thoughts on “Changing State with the Switch Expression (C# 8)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.