Skip to content

C# 8 Pattern Matching

2022-02-15

This serves as introductory documentation to the pattern matching features introduced in C# 8, which I think are particularly useful.

Deconstructors & Positional Patterns

Adding a deconstructor to your class can be done as below. It must be named Deconstruct and be a public void. Any values we want deconstructed can be set as out parameters, then populated. Here all properties are being deconstructed, but a subset could be deconstructed if desired.

class Student
{
    public string Name { get; set; }
    public int Year { get; set; }
    public Teacher FormTutor { get; set; }

    public void Deconstruct (out string name, out int year, out Teacher formTutor) 
    {
        name = Name;
        year = Year;
        formTutor = FormTutor;
    }
}

To use positional parameters, I'll also set up a second model.

class Teacher
{
    public string Name { get; set; }
    public string Subject { get; set; }

    public void Deconstruct (out string name, out string subject) 
    {
        name = Name;
        subject = Subject;
    }
}

Here is an example positional pattern. The discards (_) are used to 'match all'.

public static class YourClassHere
{
    public static bool IsInYear9English(Student student)
    {
        return student is Student(
            _,
            9,
            Teacher (_, "English")
        );
    }
}

While an if statement would probably be easier to read and maintain in this example, positional patterns enable recursion to be used, which can be very useful.

Property Patterns

I've updated the Student model to add a School property.

1
2
3
4
5
6
7
class Student
{
    public string Name { get; set; }
    public int Year { get; set; }
    public string School { get; set; }
    public Teacher FormTutor { get; set; }
}

I prefer using property patterns to positional patterns as they are much more readable. Here's an example method to check whether a student attends a particular school and has a form tutor who teaches Maths.

public static class YourClassHere
{
    public static bool IsStudentInGreenAcademyWithMathsFormTutor(Student student)
    {
        return student is {
            School: "Green Academy", 
            FormTutor: {
                Subject: "Maths"
            } 
        };
    }
}

This can be made more generic to accept an object rather than a Student, and check that object is a Student.

public static class YourClassHere
{
    public static bool IsStudentInGreenAcademyWithMathsFormTutor(object obj)
    {
        return obj is Student student && 
            student is {
                School: "Green Academy", 
                FormTutor: {
                    Subject: "Maths"
                } 
            };
    }
}

Switch Expressions

These can be used in place of standard switch cases. I'm using a discard (_) for catching the default case for unmatched patterns.

public static class YourClassHere
{
    public static string DisplayPersonInfo (object person)
    {
        string result = person switch
        {
            Student student => $"Student at {student.School} in Year {student.Year}",
            Teacher teacher => $"Teacher of {teacher.Subject}",
            _ => "Person is not a student or a teacher"
        };

        return result;
    }
}

The syntax can make it easier to read, and it can be a lot more powerful. You can also define recursive switch patterns.

public static class YourClassHere
{
    public static string DisplayPersonInfo (object person)
    {
        string result = person switch
        {
            Student student => student switch
            {
                _ when student.Year < 10 and student.Year > 6 => "Student in Key Stage 3",
                _ when student.Year >= 10 and student.Year <= 13> => "Student in Key Stage 4",
                _ => $"Student in Year {student.Year}"
            },
            Teacher teacher => $"Teacher of {teacher.Subject}",
            _ => "Person is not a student or a teacher"
        };

        return result;
    }
}

Tuple Patterns (with Switch Expressions)

You can also use Switch Expressions with Tuples to write even more useful code. For example, you could be creating a game with crafting, combining two items to make another.

public static class YourClassHere
{
    public static CraftingMaterial GetCraftingMaterial (CraftingMaterial item1, CraftingMaterial item2)
    {
        return (item1, item2) switch
        {
            // Match the items in both positions
            (CraftingMaterial.MountainFlowers, CraftingMaterial.SoulGems) => CraftingMaterial.DwemerMetal,
            (CraftingMaterial.SoulGems, CraftingMaterial.MountainFlowers) => CraftingMaterial.DwemerMetal,

            (CraftingMaterial.Ore, CraftingMaterial.DwemerMetal) => CraftingMaterial.Ingots,
            (CraftingMaterial.DwemerMetal, CraftingMaterial.Ore) => CraftingMaterial.Ingots,

            // Handle both items being the same (discard for both, to match any)
            (_, _) when item1 == item2 => item1,

            // Default case (with discard)
            _ => CraftingMaterial.Unknown
        };
    }
}

This is lovely and easy to read, as well as very powerful.