diff --git a/docs/web-dev/dotnet-csharp/pattern-matching.md b/docs/web-dev/dotnet-csharp/pattern-matching.md new file mode 100644 index 0000000..d23aa73 --- /dev/null +++ b/docs/web-dev/dotnet-csharp/pattern-matching.md @@ -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.