feat(*): Add csharp 8 pattern matching

This commit is contained in:
Josh Creek
2022-02-15 18:22:44 +00:00
parent 0620d3abb5
commit d4836dbf17
@@ -0,0 +1,192 @@
---
layout: post
parent: Web Development
nav_order: 3
title: "C# 8 Pattern Matching"
date: 2022-02-15 19:07:21 +0000
categories: dotnet csharp
---
# {{page.title}}
_{{page.date}}_
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.
```csharp
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.
```csharp
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'.
```csharp
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.
```csharp
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.
```csharp
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.
```csharp
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.
```csharp
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.
```csharp
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.
```csharp
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.