In the previous article, we looked at C# 8 asynchronous streams. Another new C# 8 feature is extended support for pattern matching. In this article, we’ll take a look at what was possible with C# 7 and what was added in C# 8.
C# 7 pattern matching
Pattern matching is a feature that was introduced in C# 7. It allows you to check whether an object is of a particular type and check its value in a concise way through the use of is
patterns and case
patterns.
The is
pattern
The is
pattern allows you to check whether a variable is of a certain type, and then assign it to a new variable. Further checks can then be made on that variable:
if (input is int count && count > 100)
This pattern can also be used to check if a variable is null
:
if (input is null)
This second statement is guaranteed to do a null
reference check. When you use == null
, the operator==
might be overloaded, causing a different check to be performed.
The case
pattern
The switch
statement cases also support patterns. These patterns can include a type check, plus additional conditions:
switch (i) { case int n when n > 100: ... case Car c: ... case null: ... case var j when (j.Equals(10)): ... default: ... }
In the examples above, you see the case
statement for null
, default
, a type check, conditions, and using conditions without a type check (case var
). Note that case var
can also match null
, so to avoid that happening we’ve placed it below case null
.
The classic switch
statement allowed only constants. Because of the dynamic conditions, the order of pattern cases matters.
C# 8 pattern matching
C# 8 expands the support for patterns and where they can be used.
switch
expressions
A switch
expression is a concise way to return a specific value based on another value:
var rgbColor = knownColor switch { KnownColor.Red => new RGBColor(0xFF, 0x00, 0x00), KnownColor.Green => new RGBColor(0x00, 0xFF, 0x00), ... _ => throw new ArgumentException(message: "invalid enum value", paramName: nameof(knownColor)), };
A regular switch
does not return a value. This syntax is more concise. There are no case
keywords, and the default
case was replaced with a discard (_
).
The case
conditions can be patterns. It’s not possible to include statements for handling a case. For each case, a single expression must be provided that represents the resulting value. This expression can be a switch
expression.
It's also possible to start from multiple input values by collecting them into a tuple:
public decimal GetDiscount(CustomerType customer, DiscountPeriod period) => (customer, period) switch { (CustomerType.Gold, DiscountPeriod.Christmas) => 0.2m, (CustomerType.Silver, DiscountPeriod.Christmas) => 0.1m, (_, DiscountPeriod.Christmas) => 0.05m, (_, _) => 0m, };
The conditions for the switch
are now also tuples. Their items have patterns that are matched against the corresponding input tuple element. In the example, we've used the discard (_
) to ignore a tuple item. Other patterns can also be used.
The is
keyword can also be used with tuple patterns.
Tuple patterns can also be used against types that are deconstructable to a tuple:
static Sector GetSector(Point point) => point switch { (0, 0) => Sector.Origin, (2, _) => Sector.One, var (x, y) when x > 0 && y > 0 => Sector.Two, (1, var y) when y < 0 => Sector.Three, _ => Sector.Unknown };
Property patterns
Property patterns express a property that needs to have a specific constant value:
switch (location) { case { State: "MN" }: ... }
The above case
will match when location.State
equals MN
. Property patterns can be used in switch expressions also.
A special case is the { }
pattern, which means: not null
. This pattern can also be used with the is
keyword:
if (location is { State: "MN" })
We can check both on the type and property, for example:
switch (vehicle) { case Taxi { Occupants: 2 } t: ... }
Conclusion
In this article, we’ve looked at C#’s support for pattern matching. Pattern matching provides us with a concise syntax match against a type, checks properties, and combines these patterns with additional conditions. In the next article, we'll explore the enhancements for C# 8's default interface methods.
C# 8 can be used with the .NET Core 3.1 SDK, which is available on Red Hat Enterprise Linux, Fedora, Windows, macOS, and other Linux distributions.
Last updated: March 29, 2023