mirror of
https://github.com/jcreek/jcreek.github.io.git
synced 2026-07-12 18:43:50 +00:00
192 lines
5.4 KiB
Markdown
192 lines
5.4 KiB
Markdown
---
|
|
tags:
|
|
- web development
|
|
- dotnet
|
|
- c#
|
|
- pattern matching
|
|
---
|
|
|
|
# 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.
|
|
|
|
```csharp linenums="1"
|
|
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 linenums="1"
|
|
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 linenums="1"
|
|
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 linenums="1"
|
|
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 linenums="1"
|
|
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 linenums="1"
|
|
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 linenums="1"
|
|
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 linenums="1"
|
|
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 linenums="1"
|
|
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.
|