Pattern matching
C# 7.0 introduces an aspect common to functional programming languages with pattern matching. This new kind of construct can test values in different ways. To accomplish this, two language constructs in C# 7.0 have been enhanced to take advantage of patterns. These are as follows:
- The
is
expression - The
case
clause inswitch
statements
With regard to the is expression, developers can now have a pattern on the right instead of just a type. When it comes to switch
statements, the case
clause can now match on patterns. The switch
statement is no longer limited to primitive types and can switch on anything. Let's start by looking at the is
expression.
Getting ready
To illustrate the concept of pattern matching, assume the following scenario. We have two object types called Student
and Professor
. We want to minimize code, so we want to create a single method to output the data from the object passed to it. This object can be a Student
or a Professor
object. The method needs to figure out which object it is working with and act accordingly. But first, we need to do a few things inside our console application to set things up:
- Ensure that you have added the following
using
statement.
using System.Collections.Generic;
- You now need to create two new classes called
Student
andProfessor
. The code for theStudent
class needs to look as follows:
public class Student { public string Name { get; set; } public string LastName { get; set; } public List<int> CourseCodes { get; set; } }
- Next, the code for the
Professor
class needs to look as follows:
public class Professor { public string Name { get; set; } public string LastName { get; set; } public List<string> TeachesSubjects { get; set; } }
To understand where we are going with pattern matching, we first need to understand where we have come from. I will start the next section off by showing you how developers might have written this code before C# 7.0.
How to do it...
- In the
Chapter1
class, create a new method calledOutputInformation()
that takes a person object as parameter.
public void OutputInformation(object person) { }
- Inside this method, we would need to check what type of object is passed to it. Traditionally, we would need to do the following:
if (person is Student) { Student student = (Student)person; WriteLine($"Student {student.Name} {student.LastName} is enrolled for courses {String.Join<int>( ", ", student.CourseCodes)}"); } if (person is Professor) { Professor prof = (Professor)person; WriteLine($"Professor {prof.Name} {prof.LastName} teaches {String.Join<string>(",", prof.TeachesSubjects)}"); }
- We have two
if
statements. We are expecting either aStudent
object or aProfessor
object. The completeOutputInformation()
method should look as follows:
public void OutputInformation(object person) { if (person is Student) { Student student = (Student)person; WriteLine($"Student {student.Name} {student.LastName} is enrolled for courses {String.Join<int> (", ", student.CourseCodes)}"); } if (person is Professor) { Professor prof = (Professor)person; WriteLine($"Professor {prof.Name} {prof.LastName} teaches {String.Join<string> (",", prof.TeachesSubjects)}"); } }
- Calling this method from the
static void Main
is easy enough. The objects are similar, but differ in the list they contain. AStudent
object exposes a list of course codes, while aProfessor
exposes a list of subjects taught to students.
static void Main(string[] args) { Chapter1 ch1 = new Chapter1(); Student student = new Student(); student.Name = "Dirk"; student.LastName = "Strauss"; student.CourseCodes = new List<int> { 203, 202, 101 }; ch1.OutputInformation(student); Professor prof = new Professor(); prof.Name = "Reinhardt"; prof.LastName = "Botha"; prof.TeachesSubjects = new List<string> { "Mobile Development", "Cryptography" }; ch1.OutputInformation(prof); }
- Run the console application and see the
OutputInformation()
method in action.

- While the information we see in the console application is what we expect, we can simplify the code in the
OutputInformation()
method much more with pattern matching. To do this, modify the code as follows:
if (person is Student student) { } if (person is Professor prof) { }
- The first
if
expression checks to see if the objectperson
is of typeStudent
. If so, it stores that value in thestudent
variable. The same logic is true for the secondif
expression. If true, the value ofperson
is stored inside the variableprof
. For code execution to reach the code between the curly braces of eachif
expression, the condition had to evaluate to true. We can, therefore, dispense with the cast of theperson
object to aStudent
orProfessor
type, and just use thestudent
orprof
variable directly, like so:
if (person is Student student) { WriteLine($"Student {student.Name} {student.LastName} is enrolled for courses {String.Join<int> (", ", student.CourseCodes)}"); } if (person is Professor prof) { WriteLine($"Professor {prof.Name} {prof.LastName} teaches {String.Join<string> (",", prof.TeachesSubjects)}"); }
- Running the console application again, you will see that the output is exactly the same as before. We have, however, written better code that uses type pattern matching to determine the correct output to display.

- Patterns, however, don't stop there. You can also use them in constant patterns, which are the simplest type of pattern to use. Let's take a look at the check for the constant
null
. With pattern matching we can enhance ourOutputInformation()
method as follows:
public void OutputInformation(object person) { if (person is null) { WriteLine($"Object {nameof(person)} is null"); } }
- Change the code that is calling the
OutputInformation()
method and set it tonull
.
Student student = null;
- Run your console application and see the message displayed.

Note
It is good practice to use the nameof
keyword here. If the variable name person
ever has to change, the corresponding output will be changed also.
- Lastly,
switch
statements in C# 7.0 have been improved to make use of pattern matching. C# 7.0 allows us to switch on anything, not just primitive types and strings. Thecase
clauses now make use of patterns, which is really exciting. Let's have a look at how to implement this in the following code examples. We will keep using theStudent
andProfessor
types to illustrate the concept of pattern matching inswitch
statements. Modify theOutputInformation()
method and include the boilerplateswitch
statement as follows. Theswitch
statement still has defaults, but it can now do so much more.
public void OutputInformation(object person) { switch (person) { default: WriteLine("Unknown object detected"); break; } }
- We can expand the
case
statement to check for theProfessor
type. If it matches an object to theProfessor
type, it can act on that object and use it as aProfessor
type in the body of thecase
statement. This means we can call theProfessor
-specificTeachesSubjects
property. We do it like this:
switch (person) { case Professor prof: WriteLine($"Professor {prof.Name} {prof.LastName} teaches {String.Join<string> (",", prof.TeachesSubjects)}"); break; default: WriteLine("Unknown object detected"); break; }
- We can also do the same for
Student
types. Change the code of theswitch
as follows:
switch (person) { case Student student: WriteLine($"Student {student.Name} {student.LastName} is enrolled for courses {String.Join<int> (", ", student.CourseCodes)}"); break; case Professor prof: WriteLine($"Professor {prof.Name} {prof.LastName} teaches {String.Join<string> (",", prof.TeachesSubjects)}"); break; default: WriteLine("Unknown object detected"); break; }
- One final (and great) feature of
case
statements remains to be illustrated. We can also implement awhen
condition, similar to what we saw in C# 6.0 with exception filters. Thewhen
condition simply evaluates to a Boolean and further filters the input that it triggers on. To see this in action, change theswitch
accordingly:
switch (person) { case Student student when (student.CourseCodes.Contains(203)): WriteLine($"Student {student.Name} {student.LastName} is enrolled for course 203."); break; case Student student: WriteLine($"Student {student.Name} {student.LastName} is enrolled for courses {String.Join<int> (", ", student.CourseCodes)}"); break; case Professor prof: WriteLine($"Professor {prof.Name} {prof.LastName} teaches {String.Join<string>(",", prof.TeachesSubjects)}"); break; default: WriteLine("Unknown object detected"); break; }
- Lastly, to come full circle and check for null values, we can modify our
switch
statement to cater for those too. The completedswitch
statement is, therefore, as follows:
switch (person) { case Student student when (student.CourseCodes.Contains(203)): WriteLine($"Student {student.Name} {student.LastName} is enrolled for course 203."); break; case Student student: WriteLine($"Student {student.Name} {student.LastName} is enrolled for courses {String.Join<int> (", ", student.CourseCodes)}"); break; case Professor prof: WriteLine($"Professor {prof.Name} {prof.LastName} teaches {String.Join<string> (",", prof.TeachesSubjects)}"); break; case null: WriteLine($"Object {nameof(person)} is null"); break; default: WriteLine("Unknown object detected"); break; }
- Running the console application again, you will see that the first case statement containing the
when
condition is triggered for theStudent
type.

How it works...
With pattern matching, we saw that patterns are used to test whether a value is of a certain type.
Note
You will also hear some developers say that they test whether the value has a certain shape.
When we find a match we can get to the information specific to that type (or shape). We saw this in the code where we accessed the CourseCodes
property, which was specific to the Student
type and the TeachesSubjects
property, which was specific to the Professor
type.
Lastly, you now need to pay careful attention to the order of your case
statements, which now matters. The case
statement that uses the when
clause is more specific than the statement that simply checks for a Student
type. This means that the when
case needs to happen before the Student
case because both of these cases are of type Student
. If the Student
case happens before the when
clause, it will never trigger the switch
for Students
that have course code 203.
Another important thing to remember is that the default
clause will always be evaluated last, irrespective of where it appears in the switch
statement. It is, therefore, good practice to write it as the last clause in a switch
statement.