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).