C# 7.0 – Pattern Matching

I already explained binary literals, digit separators, tuples, out vars and ref returns, and expression bodied members – great new C# 7.0 features – in previous articles. Beside tuples, the biggest feature of C# 7.0 is pattern matching.

Connect

Pattern Matching

Often you need to work with the object type. It would be a lot easier to always use strongly typed interfaces and classes. However, this is not always the case. Also with interfaces and classes, sometimes you need to check for different interfaces or derived types to invoke other class members. The hierarchy of the types could be done differently to avoid such cases. However, often this is not practical, or a library used does not offer other ways features. Here, pattern matching can be applied.

C# 7.0 offers the first features for pattern matching: the is operator and the switch statement have been enhanced with the const pattern, the type pattern, and the var pattern.

Using the is Operator with Pattern Matching

The is operator is available since the first C# version. This operator can be used to check if an object is compatible with a specific type, e.g. if a specified interface is implemented, or if the type of the object derives from a base class. The result from this operator is true or false.

With the C# 7.0 extensions, the is operator can be used to check for patterns.

In the sample code, I’m creating an object array that consists of null, a number, and two Person objects. With every item of this list, the method IsPattern is invoked. This method demonstrates the pattern matching features of the is operator.

object[] data = { null, 42, new Person("Matthias Nagel"), new Person("Katharina Nagel") };

foreach (var item in data)
{
    IsPattern(item);
}

Const Pattern

One available pattern with C# 7.0 is the const pattern. In the first sample I’m checking for null.

public static void IsPattern(object o)
{
    if (o is null) Console.WriteLine("it's a const pattern");
    //...
}

Without C# 7.0, you could do such checks using the == operator. Of course, you often checked for null throwing an ArgumentNullException. This code can now be changed to use the is operator instead.

public static void IsPattern(object o)
{
    if (o is null) throw new ArgumentNullException(nameof(o));
    //...
}

With a const pattern you can also verify for a const number, such as 42:

if (o is 42) Console.WriteLine("it's 42");

Type Pattern

With the type pattern, you can verify if the object is compatible with the specified type. What’s different to the syntax available before C# 7.0 is that if the pattern matches, the object is assigned to a new variable of the specified type.

if (o is int i) Console.WriteLine($"it's a type pattern with an int and the value {i}");

This is very practical, as you can strongly-typed access the members of the type, e.g. with the Person type:

if (o is Person p) Console.WriteLine($"it's a person: {p.FirstName}");

Not directly related to pattern matching, but of course you can also use logical operators to further reduce the matches:

if (o is Person p2 && p2.FirstName.StartsWith("Ka")) 
    Console.WriteLine($"it's a person starting with Ka {p2.FirstName}");

Var Pattern

Using the var keyword for the type, this is the var pattern. This pattern always succeeds, as the object is always of a type. The advantage is that the variable declared with the var keyword is of the real type of the object, as you can see invoking the GetType method with the Name property.

if (o is var x) Console.WriteLine($"it's a var pattern with the type {x?.GetType()?.Name}");

In case o is null, invoking the GetType method on null throws a NullReferenceException. To avoid this exception, the null conditional operator is used that was introduced with C# 6.

Using the switch Statement with Pattern Matching

The switch statement has been enhanced as well. Using case, you can check for a const pattern (this was possible before), a type pattern to immediately declare a variable of the specified type, and the var pattern.

With the following code snippet pay special attention to the when keyword. You can enhance the pattern filter to only apply when a specific condition is fulfilled. Here, the when keyword is used – the same keyword already used with exception filters that are available since C# 6. With this you can define different cases for the same type. The first case that applies is taken.

public static void SwitchPattern(object o)
{
    switch (o)
    {
        case null:
            Console.WriteLine("it's a constant pattern");
            break;
        case int i:
            Console.WriteLine("it's an int");
            break;
        case Person p when p.FirstName.StartsWith("Ka"):
            Console.WriteLine($"a Ka person {p.FirstName}");
            break;
        case Person p:
            Console.WriteLine($"any other person {p.FirstName}");
            break;
        case var x:
            Console.WriteLine($"it's a var pattern with the type {x?.GetType().Name} ");
            break;
        default:
            break;
    }
}

Putting the generic catch for a Person before the catch with the specified when results in a compilation error. In such a scenario, the filtered catch would never apply because the generic one is taken first, and this results in the compiler error CS8120, “The switch case has already been handled by a previous case.”.

Summary

Pattern matching gives another C# 7.0 feature that can simplify and reduce your code with ideas from functional programming. is and switch/case have been enhanced to support const, type, and var patterns.

Pattern matching as it is implemented in C# 7.0 is just a begin for this feature. Sub-patterns, property patterns, positional patterns, and recursive patterns might be added to a future C# version.

Sample Code

The sample code is available at GitHub.

To run the sample code you need Visual Studio 2017 RC.

Have fun with programming and learning!
Christian

More Information

Update: more information on C# 7 features in my new book Professional C# 7 and .NET Core 2.0 with source code updates at GitHub.

More information about C# is available in my new book and my C# workshops:

Professional C# 6 and .NET Core 1.0

Christian Nagel’s Workshops

Null Conditional Operator

Pattern Matching Proposal – an out of data proposal, but it shows what additional pattern matching features could come in a future C# version.

More C# 7.0 Features:

Tuples

Binary Literals and Digit Separators

Out Vars and Ref Returns

Expression Bodied Members

Image from © Fantasista | Dreamstime.com Communication

16 thoughts on “C# 7.0 – Pattern Matching

  1. imagine if they added “is not” functionality, C# looking like VB everyday, I’m sure they will also port that switch statement over to the better case statement, it’s only a matter of time, LOL.

    Like

  2. Nice article, but one criticism:

    public static void NullCheck(object o)
    {
    if (o == null) throw new ArgumentNullException(nameof(o));
    //…
    }

    That isn’t especially useful, it’s going to throw ArgumentNullException all right, but it’s going to tell you that your argument name is “o” for anything you use it with. I appreciate it stops some code repetitiveness, but at the cost of usefulness in the event of such an exception.

    Like

    1. Just check the documentation of the ArgumentNullException class, the overloaded version with the string parameter has the name paramName, and from the documentation: “Initializes a new instance of the ArgumentNullException class with the name of the parameter that causes this exception.”.
      Why is this useful? The programmer calling this method knows the parameter where he passed null which is not allowed.

      Like

      1. I’m not disagreeing with the purpose of throwing an ArgumentNullException, Christian, I’m saying that using your example the exception is going to have the message “Parameter name: o” for every ArgumentNullException thrown. You’ll save on code reuse but in the event of a Null Exception, all your logs and stack traces are going to blame parameter name “o”.

        nameof(o); is a compile time construct, it doesn’t magically find the “name” of o, it’ll just turn

        throw new ArgumentNullException(nameof(o));

        into

        throw new ArgumentNullException(“o”);

        Like

  3. Of course it turns into
    throw new ArgumentNullException(“o”);
    and with this method, the parameter name is always “o”, as it is defined with the declaration.
    I just prefer nameof(o) instead of “o”. If you change the parameter name “o” to something else, with refactoring all occurrences of “o” are changed – including nameof(o), and you get a compilation error if it is not changed. If you use “o” instead, a parameter name change is easily missed.

    Like

    1. Christian, I think you are missing my point. I have nothing against nameof. That’s not what I am saying.

      What’s the point in passing in the nameof parameter, or indeed any kind of parameter, if it’s going to be the same for every null exception you throw?

      Imagine you have a method with 3 parameters:

      void MyMethod(string paramA, string paramB, string paramC);

      Traditionally, you’d check each param and throw a null ref exception, i.e.

      if(paramA == null)
      {
      throw new ArgumentNullException(nameof(paramA));
      }

      if(paramB == null)
      {
      throw new ArgumentNullException(nameof(paramB));
      }

      if(paramC == null)
      {
      throw new ArgumentNullException(nameof(paramC));
      }

      Right? and if any of them is null, you’ll get an exception error saying something like….

      System.ArgumentNullException: Value cannot be null.
      Parameter name: paramA

      Which is great, it’ll tell you exactly which param is null.

      However, using your method, you do this:

      void MyMethod(string paramA, string paramB, string paramC)
      {
      NullCheck(paramA);
      NullCheck(paramB);
      NullCheck(paramC);

      ….
      };

      Which, great – saves you a load of space and looks good. Except when a param is null, you’ll get this:

      System.ArgumentNullException: Value cannot be null.
      Parameter name: o

      Which one is null? That’s my point, you can’t tell.

      Like

      1. I see your case now, but this was not how it was meant. It was never meant to replace the check for a null with the sample method NullCheck, that’s why I didn’t understand your case.
        The method NullCheck was just meant to show throwing a NullReferenceException instead of a Console.WriteLine in the previous method. I will rename the method NullCheck to IsPattern, to make this more clear.

        Like

Leave a comment

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