A while back I wrote a post C# 8.0 enhancements with pattern matching which was very light on the subject. Let’s look a little more in depth at options for pattern matching.
is/as in pattern matching syntax
Often (if you’ve been writing C# for a while) you’ll used to writing code like this
var person = o as Person; if (person != null) { Console.WriteLine(person.FirstName); }
i.e. we have an object o which we don’t know the type of, so we use as to convert this to a Person type or null if it’s not of that type.
This can be replaced with a slightly more concise syntax (and what’s known as the declaration pattern).
if (o is Person person) { Console.WriteLine(person.FirstName); }
Null checks
This is a pattern known as a constant pattern
if (o is null) { Console.WriteLine("Is null"); }
Along with a logical pattern using not we can also write
if (o is not null) { Console.WriteLine("Is not null"); }
We can also use this pattern with types, for example
if (o is not (string or null)) { Console.WriteLine("Is NOT string or null"); } <strong>Pattern matching when values match a criteria</strong> If we extend the above Pattern Matching to not just check the type is a Person but also that the Age property is greater than 4, so we can now replace [code language="csharp"] if (o is Person p && p.Age > 4) { Console.WriteLine($"Older than 4 {p.FirstName}"); }
with the following
if (o is Person { Age: > 4 } p) { Console.WriteLine($"Older than 4 {p.FirstName}"); }
In the above we’re using a property pattern.
Switch patterns matching
Exhaustive switches can be used to match types using switch statements, for example
var result = o switch { string s => $"String {s}", Person p => $"Person {p.FirstName}", _ => throw new ArgumentException("Unhandled type") };
Note the use of the _ (discard) ensuring this is an exhaustive switch.
Better still we can also use other features of pattern matching in the switch like this
var result = o switch { string s => $"String {s}", Person { Age: > 4} p => $"Person {p.FirstName} > 4", Person p => $"Person {p.FirstName}", null => "In Null", _ => throw new ArgumentException("Unhandled type") };
In the above we’re switching based upon type and also matching values.
We can also use relational patterns, in this example we’ll assume o is an int
var result = o switch { 1 or 2 => "One or Two", > 2 and < 4 => "Mid", >= 4 and < 6 => "High", 6 => "Six", _ => "Other" };
Before we leave the switch statement we can also match against types using the “standard” switch syntax, i.e.
switch (o) { case string s: Console.WriteLine(s); break; case Person p: Console.WriteLine(p.FirstName); break; }
Tuples
Pattern matching also allows us to match against tuples and use the discard to ignore parts we’re not interested in, for example
var result = o switch { (1, _) => "First One", (_, 0) => "Second Zero", _ => "Other" };
Creating new variables from patterns
We can also use the var pattern to assign values from the patterns
var result = o switch { var (a, b) when a < b => new Tuple<string, int, int>("First less than second", a, b), var (a, b) when a > b => new Tuple<string, int, int>("Second greater than first", a, b), var (a, b) => new Tuple<string, int, int>("Equals", a, b) };
In this example we’re deconstructing a tuple and assigning to some new value (in this case an extended Tuple, just because I couldn’t think of a better example – check the documentation link above for a better example).