Share via



May 2019

Volume 34 Number 5

[C# 8.0]

Pattern Matching in C# 8.0

By Filip Ekberg | May 2019

Over the years we’ve seen a ton of features in C# that improve not only the performance of our code, but more importantly its readability. Given the fast pace of the software industry, the language certainly needs to keep up and evolve with its user base. Something that’s been widely used in different programming languages, such as F#, Swift or Kotlin sometimes find its way into C#. One of these being pattern matching—a concept that has been around for a long time, and something for which a lot of developers in the C# space have long waited.

As of C# 7.0, developers got a taste of the power that comes with pattern matching. We saw a pattern starting to take form, which later has become an extremely powerful and interesting addition to the language. Just as other language features have drastically changed the way we write our software, I expect that pattern matching in C# will have a similar effect.

Do we really need another language feature, though? Can’t we just use traditional approaches? Of course we could. Although an addition like pattern matching will most definitely change the way a lot of us choose to write our code, the same question could be said for other language features that have been introduced over the years.

One that changed the C# language drastically was the introduction of Language-Integrated Query (LINQ). Nowadays when processing data, people choose which flavor they personally like. Some choose to use LINQ, which in some cases constructs less-verbose code, while others opt for traditional loops. I expect similar uptake for pattern matching, as the new functionality will change the way developers work as they move away from more verbose approaches. Mind you, the traditional approaches aren’t going anywhere, as many developers will opt to stick with tried-and-true solutions. But the additional language features should offer a way to complement C# code projects, rather than deprecate current code.

Introducing Pattern Matching

If you’ve ever tried languages like F# or Kotlin, you’ve likely seen examples of pattern matching in action. It’s very commonly used among a lot of different programming languages on the market—mostly, of course, to make the code a bit more readable. So what is pattern matching?

It’s rather simple. You look at a given structure and based on the way it looks, you identify it and you then can immediately use it. If you get a bag of fruit, you look down and immediately see the difference between the apples and the pears. Even if both are green. At the same time, you can look down into your bag of fruit and identify which fruits are green, as we know all fruits have a color.

Distinguishing between the type of fruit and an attribute of the fruit is exactly what pattern matching is about. Developers have different ways of expressing themselves when identifying this.

Traditionally, I could check all of this using a simple condition. But what if I need to explicitly use the apple? I’d end up in a situation where I must first validate the type, attribute and then cast to an apple. This code quickly ends up a bit messy and, frankly, it’s error prone.

Here’s an example where I validate the specific type of fruit to be an apple. I apply an attribute constraint, and then to use it I have to cast it, like so:

if(fruit.GetType() == typeof(Apple) && fruit.Color == Color.Green)
{
  var apple = fruit as Apple;
}

Another approach I could take is to use the is keyword, which gives me a little bit more flexibility. As opposed to the previous example, the is keyword will also match on derived apples:

if(fruit is Apple)
{
  MakeApplePieFrom(fruit as Apple);
}

In this case, if the fruit is a derived type of apple, I’ll be able to make an apple pie out of it. Whereas in the earlier example, it would have to be a very specific type of apple.

Fortunately, there’s a better way. As I mentioned, languages like Swift and Kotlin allow you to use pattern matching. For its part, C# 7.0 introduced a lightweight version of pattern matching that can be helpful, though it lacks many of the nice features present in other languages. You can refactor the previous expression into the C# 7.0 code that follows, which allows you to use a switch to match your different patterns. It isn’t perfect, but it does improve on what was previously available. Here’s the code:

switch(fruit)
{
  case Apple apple:
    MakeApplePieFrom(apple);
    break;
  default:
    break;
}

A few things here are interesting. First, notice that I don’t have a single type cast anywhere in this code, and also that I can use the apple just matched on in the case context. Just as with the is keyword, this will match on derived apples, as well.

This C# 7.0 code also reads better, and is much easier to have conversations around, than similar code in C# 6.0. The code is just saying, “Based on the fact that fruit is an apple, I want to use this apple.” Each case can match on a type that shares similar traits, meaning they inherit from the same class, for instance, or implement the same interface. In this case, an apple, pear and banana are all fruits.

What’s missing is a way to filter out the green apples. Have you seen exception filters? That’s a feature introduced in C# 6.0 that allows you to catch certain exceptions only when a certain condition is met. This feature introduced the when keyword, which is applicable in pattern matching, as well. I can match the apple using pattern matching, and only enter the case when the condition is met. Figure 1 shows this.

Figure 1 Applying a Filter Using the When Keyword

Fruit fruit = new Apple { Color = Color.Green };
switch (fruit)
{
  case Apple apple when apple.Color == Color.Green:
    MakeApplePieFrom(apple);
    break;
  case Apple apple when apple.Color == Color.Brown:
    ThrowAway(apple);
    break;
  case Apple apple:
    Eat(apple);
    break;
  case Orange orange:
    orange.Peel();
    break;
}

As Figure 1 illustrates, the order matters. I first look for an apple with the color green, because this characteristic is the most important to me. If there’s another color, let’s say brown, this would indicate that my apple has gone bad and I want to throw it out. As for all other apples, I don’t want them in the pie, so I’ll just eat them. The final apple pattern is a “catch all” for all apples that have neither a green nor a brown color.

You’ll also see that if I get an orange, I’ll just peel the skin off. I’m not limited to handling one particular type; as long as the types all inherit from fruit, we’re good to go.

Everything else works like the normal switch that you’ve been using since C# 1.0. This example was written entirely in C# 7.0, so the question is, is there room for improvement? I would say so. The code is still a bit on the expressive side, and it could be made more readable by improving the way patterns are expressed. Also, it would help to have other ways to express constraints against what my data “looks like.” Let’s now jump into C# 8.0 and look at the changes that have been introduced to make our lives easier.

The Evolution of Pattern Matching in C# 8.0

The latest version of C#, currently in preview, introduces some important pattern-matching improvements. To try C# 8.0, you’ll have to use Visual Studio 2019 Preview, or enable the preview language features in Visual Studio 2019. The general availability of C# 8.0 is later this year, expected at the same time that .NET Core 3.0 ships. How can we find new ways to express a constraint on the properties of a type? How can we make the expression of block patterns more intuitive and readable? In C# 8.0 the language takes another step forward to introduce a way to work with patterns that should be very familiar to those who’ve worked in languages like Kotlin. These are all wonderful additions that make the code readable and maintainable.

First, we now have an option to use something called a switch expression, instead of the traditional switch statement that developers have been using since C# 1.0. Here’s an example of a switch expression in C# 8.0:

var whatFruit = fruit switch {
  Apple _ => "This is an apple",
  _ => "This is not an apple"
};

As you can see, instead of having to write case and break for each different match, I simply use a pattern and an expression. When I match for a fruit, the underscore (_) means that I don’t care about the actual fruit that I matched on. In fact, it doesn’t have to be an initialized type of fruit. The underscore will match on null, as well. Think of this as simply matching on the specific type. When I found this apple, I returned a string using an expression—much like the expression-bodied members that were introduced in C# 6.0.

This is about more than just saving characters. Imagine the possibilities here. For example, I could now introduce an expression-bodied member that includes one of these switch expressions, which also leverages the power of pattern matching, like so:

public Fruit Fruit { get; set; }
public string WhatFruit => Fruit switch
{
  Apple _ => "This is an apple",
  _ => "This is not an apple"
};

This can get really interesting and powerful. The following code shows how you would perform this pattern match in the traditional manner. Have a look and decide which one you would prefer:

public string WhatFruit
{
  get
  {
    if(Fruit is Apple)
    {
      return "This is an apple";
    }
    return "This is not an apple";
  }
}

Obviously, this is a very simple scenario. Imagine when I introduce constraints, multiple types to match against, and then use the casted type within the condition context. Sold on the idea yet? I thought so!

While this is a welcome addition to the language, please resist the urge to use switch expressions for every if/else condition. For an example of what not to do, check out the following code:

bool? visible = false;
var visibility = visible switch
{
  true => "Visible",
  false => "Hidden",
  null, _ => "Blink"
};

This code indicates that you could have four cases for a nullable Boolean, which of course you can’t. Just be mindful about how you use switch expressions and don’t abuse the syntax, exactly as you would with any other language feature.

I’ve already covered the fact that switch expressions can cut down the amount of code you write, as well as make that code more readable. This is true also when adding constraints to your types. The changes to pattern matching in C# 8.0 really stand out when you look at the combination of tuples, deconstruction and what’s known as recursive patterns.

Expressing Patterns

A recursive pattern is when the output of one pattern-match expression becomes the input of another pattern-match expression. This means deconstructing the object and looking at how the type, its properties, their types, and so forth are all expressed, and then applying the match to all of these. It sounds complicated, but really it’s not.

Let’s look at a different type and its structure. In Figure 2 you’ll see a rectangle that inherits from Shape. The shape is just an abstract class that introduces the property point, a way for me to get the shape onto a surface so I know where it’s supposed to go.

Figure 2 Example of Deconstruct

abstract class Shape
{
  public Point Point { get; set; }
}
class Rectangle : Shape
{
  public int Width { get; set; }
  public int Height { get; set; }
  public void Deconstruct(out int width, out int height, out Point point)
  {
    width = Width;
    height = Height;
    point = Point;
  }
}

You might wonder what the Deconstruct method in Figure 2 is all about. It allows me to “extract” the values of an instance into new variables outside of the class. It’s commonly used together with pattern matching and tuples, as you’ll discover in a moment.

So, I essentially have three new ways to express a pattern in C# 8.0, all of which have a specific use case. They are:

  • Positional pattern
  • Property pattern
  • Tuple pattern

Don’t worry, if you prefer the normal switch syntax, you can use these pattern-matching improvements with that, as well! These changes and additions to the language in terms of pattern matching are commonly referred to as recursive patterns.

The positional pattern leverages the deconstruct method that you have on your class. You can express a pattern that matches given values that you get out of the deconstruction. Given the fact that you have a way defined to deconstruct the rectangle, you can express a pattern that leverages the position of the output like what you see in Figure 3.

Figure 3 Positional Pattern

Shape shape = new Rectangle
{
  Width = 100,
  Height = 100,
  Point = new Point { X = 0, Y = 100 }
};
var result = shape switch
{
  Rectangle (100, 100, null) => "Found 100x100 rectangle without a point",
  Rectangle (100, 100, _) => "Found 100x100 rectangle",
  _ => "Different, or null shape"
};

First, let’s match the type of shape. In this case I only want to match it against a rectangle. The second applied pattern, when matched to a rectangle, uses the deconstruct method together with the tuple syntax to express which values I require for each particular position.

I can specify that I explicitly want the point to be null, or I can use the underscore to express that I simply don’t care. Keep in mind that the order matters very much here. If we’d have the version where we don’t care on top, it would always match on that pattern even if the rectangle has a point or not. This is known as the positional pattern.

This is very handy if I have a deconstruct available, although if the deconstruct outputs a lot of values, it gets rather verbose. This is where the property pattern comes into play. So far I’ve matched on different types, but some scenarios require you to match on other things, such as state or just looking at the different property values, or the lack thereof.

As the following code describes, I don’t care which type I get, as long as it matches a type containing a point, where this point has a value of 100 in the Y property, like so:

shape switch
{
  { Point: { Y : 100 } } => "Y is 100",
  { Point: null } => "Point not initialized",
};

Observe that the code doesn’t in fact handle cases where the shape is null, or when the point is initialized but has a different Y value than 100. In those situations, this code will throw an exception. This could be solved by introducing the default case using the underscore.

I could also say that I require the point to be uninitialized, and only handle those uninitialized scenarios. This is a lot less verbose than using the positional patterns, and it works very well for situations where you can’t add a deconstruct method to the type you’re matching.

Finally, I have the tuple pattern, which leverages the positional pattern and allows me to compose a tuple on which to run my match. I can illustrate this with a scenario where I operate on different states, such as opening, closing and locking a door (see Figure 4). A particular situation may occur depending on the current state of the door, the operation I want to perform and the key I may possess. This example of using the Tuple pattern to introduce a state machine is one commonly used by C# Design Lead Mads Torgersen. Check out Torgersen’s post, “Do More with Patterns in C# 8.0,” at bit.ly/2O2SDqo.

Figure 4 Tuple pattern

var newState = (state, operation, key.IsValid) switch
{
  (State.Opened, Operation.Close, _)      => State.Closed,
  (State.Opened, Operation.Open, _)       => throw new Exception(
    "Can't open an opened door"),
  (State.Opened, Operation.Lock, true)    => State.Locked,
  (State.Locked, Operation.Open, true)    => State.Opened,
  (State.Closed, Operation.Open, false)   => State.Locked,
  (State.Closed, Operation.Lock, true)    => State.Locked,
  (State.Closed, Operation.Close, _)      => throw new Exception(
    "Can't close a closed door"),
  _ => state
};

The code in Figure 4 first constructs a new tuple containing the current state, the desired operation, and a Boolean checking if the user has a valid key or not. It’s a very simple scenario.

Based on these different values, I can match on different situations by constructing more tuples, together with a positional pattern. This is the tuple pattern. If I try to open a door that’s closed, but not locked, that will result in a new state telling me that the door is now open. If the door is locked and I try to unlock it with an invalid key, the door will remain locked. If I try to open a door that’s opened, I get an exception. You get the idea. This is a very flexible and interesting way to approach a situation that previously was very verbose and produced code that was a lot less readable.

Final Words

The pattern-matching improvements in C# 8.0, together with the switch expression, will definitely change the way developers write applications. C# is nearly two decades old and has evolved to reflect the way that applications are built. Pattern matching is simply the latest expression of that evolution.

For developers, it’s wise to be cautious about overusing these new principles and patterns. Be mindful about the code that you write, and make sure that it’s readable, understandable and maintainable. That’s all that your fellow developers ask for, and I reckon that these changes to the language will help improve the signal-to-noise ratio of the code you produce.


Filip Ekberg is a public speaker, Pluralsight author, principal consultant and author of “C# Smorgasbord” (2012). Ekberg has worked all the way from Sydney to Gothenburg, and has more than a decade of experience with C#. You can contact him through Twitter: @fekberg or via filip@ekberg.dev.

Thanks to the following Microsoft technical expert for reviewing this article: Bill Wagner


Discuss this article in the MSDN Magazine forum