Working With Switch Expressions And Pattern Matching In C# 8.0

Introduction

C# 8 was officially released on September 23,  2019, along with .NET Core 3.0 and Visual Studio 2019 v16.3. It has several new features and enhancements to give more power to developers and make their day-to-day coding even easier.

I have started writing a series of articles to go through the new C# 8 features one by one to cover all the new features and enhancements. In case you have not gone through my previous articles on C# 8, you may go through as follows.

In this article, we will go through the Switch expressions and Pattern matching.

Switch expression has evolved over a few releases, and in C# 8, it has changed significantly. In the new switch expression, repetitive case and break keywords have been significantly reduced.

Let's have a look at both the traditional and new switch expressions to understand the changes. We will start with a traditional expression as follows.

private static RGBColor ExecuteOldSwitch(PrimaryColor color)
{
    switch (color)
    {
        case PrimaryColor.Red:
            return new RGBColor(0xFF, 0x00, 0x00);
        case PrimaryColor.Green:
            return new RGBColor(0x00, 0xFF, 0x00);
        case PrimaryColor.Blue:
            return new RGBColor(0x00, 0x00, 0xFF);
        default:
            throw new ArgumentException(message: "invalid color", paramName: nameof(color));
    };
}

Now let's look at the modern switch statement introduced in C# as follows.

private static RGBColor ExecuteNewSwitch(PrimaryColor color) => color switch
{
    PrimaryColor.Red => new RGBColor(0xFF, 0x00, 0x00),
    PrimaryColor.Green => new RGBColor(0x00, 0xFF, 0x00),
    PrimaryColor.Blue => new RGBColor(0x00, 0x00, 0xFF),
    _ => throw new ArgumentException(message: "invalid color", paramName: nameof(color))
};

You may have noticed that the new switch syntax is crisper and intuitive. The summary of changes can be put together as follows.

  • The variable name comes before the switch.
  • The case and elements are replaced with =>.
  • The default case has been replaced with a _ discard.
  • The bodies are no longer statements but expressions.

There are some additional patterns that emerged out of new switch statements like property, tuple, and positional patterns. Let's have a look at them one by one.

Property Pattern in C#

Property pattern helps you to compare the properties of the object.

Consider a government tax calculation site that must calculate tax based on the buyer's address. In this case, the tax amount depends on the State property of the address. The following example uses the property pattern to compute the tax from the address along with the overall price.

public static void ExecutePropertyPattern()
{
    Address address = new Address { State = "MN" };
    Console.WriteLine($"Overall price (including tax) of {address.State} is: {ComputeOverallPrice(address, 2.4M)}");
}
private static decimal ComputeOverallPrice(Address location, decimal price)
    => location switch
    {
        { State: "MN" } => price + price * 0.78M,
        { State: "MI" } => price + price * 0.06M,
        { State: "WA" } => price + price * 0.07M,
        _ => 0M
    };

Tuple Pattern in C#

Now let's have a look at the tuple pattern. It’s like a property pattern with a difference in that it uses tuple values to compare.

Let's have a look at the example as follows.

public static void ExecuteTuplePattern()
{
    (string, string, string) counties = ("India", "Australia", "England");
    var team1 = counties.Item1.ToString();
    var team2 = counties.Item3.ToString();
    Console.WriteLine($"Result of the match between {team1} and {team2} is: {ReturnWinner(team1, team2)}");
}
private static string ReturnWinner(string team1, string team2)
    => (team1, team2) switch
    {
        ("India", "Australia") => "Australia is covered by India. India wins.",
        ("Australia", "England") => "Australia breaks England. Australia wins.",
        ("India", "England") => "India covers England. India wins.",
        (_, _) => "tie"
    };

As you can see in the above code, the tuple value is being compared, and the expression of the matching tuple value is being returned to the caller. In the above example, the output would be displayed as "Result of the match between India and England is: India covers England. India wins."

Positional Pattern in C#

Apart from properties and tuples, patterns can be matched even for positions. Positional pattern matching works with the help of the deconstructor introduced in C# 7.

Deconstructor allows setting its properties into discrete variables and allows the use of positional patterns in a compact way without having to name properties.

Let's take an example to understand it more. To start with, let's create a Point class that uses the deconstruct method.

public class Point
{
    public int X { get; }
    public int Y { get; }
    public Point(int x, int y) => (X, Y) = (x, y);
    public void Deconstruct(out int x, out int y)
    {
        (x, y) = (X, Y);
    }
}

Please note that here method name Deconstruct is required by the compiler, and any other name with the same method body won't work.

We also need to have an enum to facilitate the positional comparison as follows.

public enum Quadrant
{
    Origin,
    One,
    Two,
    Three,
    Four,
    OnBorder,
    Unknown
}

Now let's have logic to find patterns and a method to call it and print on the console.

public static void ExecutePositionalPattern()
{
    Point point = new Point(5, 10);
    Console.WriteLine($"Quadrant of point {point.X} and {point.Y} is: {FindQuadrant(point)}");
}
private static Quadrant FindQuadrant(Point point) => point switch
{
    (0, 0) => Quadrant.Origin,
    var (x, y) when x > 0 && y > 0 => Quadrant.One,
    var (x, y) when x < 0 && y > 0 => Quadrant.Two,
    var (x, y) when x < 0 && y < 0 => Quadrant.Three,
    var (x, y) when x > 0 && y < 0 => Quadrant.Four,
    var (_, _) => Quadrant.OnBorder,
    _ => Quadrant.Unknown
};

The above code will output as "Quadrant of points 5 and 10 is: One" as passed points 5 and 10 fall in the first quadrant.

In the code above, the discard pattern (_) matches when either x or y is 0, but not both. An important point with switch expression is that it must either produce a value on matching cases or throw an exception if none of the cases match. Also, the compiler renders a warning if you do not include all possible cases in your switch expression.

Note. Deconstruction isn't a great choice, and it should only be used to types where it makes sense and clearer to interpret. For example, for a Point class, it was easy and intuitive to assume that the first value is X and the second is Y, so please use positional patterns carefully.

Summary

In this article, we have gone through the new switch syntax and compared it with the traditional one. We have also understood how patterns (property, tuple, and positional) can be used with a switch to make it even more powerful. Clearly, the new switch syntax is more crisp and easy to use.


Similar Articles