Pattern matching feature in .Net C# 8.0?

C#

In this post, we are going to explore the Pattern matching feature in .Net C# 8.0. First, we will go through the pattern matching works in C# 7.0. Let us understand Pattern matching in C# 7.0

Understand Pattern matching in C# 7.0

To understand the Pattern Matching, let’s begin with C# 7.0 when this was introduced for the first time in the language.As you might be aware of “is” and “switch” statements in C#, so the new concept of Pattern matching gave some extensions to these statements of the C# language. With the new extension, these statements have more the capability to: :

  • Test a variable’s value or type as a statement. // This was existing
  • Extract that value and assign it to a variable. // New extension

Let us understand each statement in detail by using some examples and see how they got transformed with Pattern matching feature enhancements:

1.The ‘is’ type pattern :

Let us consider ‘is ‘ type pattern by using an Example with ‘if’ statement.If you see the first line of code in the example below, this line is serving two extensions:

  • input is int = This is doing the test on input(type).
  • input is int count = if that test passed then it’s assigning the result to count. If the test fails it won’t assign results to count.
if(input is int count)
 sum += count;

Important Points of IS expression :

  • The new ‘is‘ expression works with value types as well as reference types. So you can also use it with objects of classes or structures etc.
  • The scope of variable ‘count‘ is limited and is the same as the scope of variables in normal if statements. So understanding the scope is important.

2.The ‘Switch’ type pattern

Let us understand the ‘switch’ type pattern with an example below:

Here again, this code in the example above is serving two extensions:

  • Switch(shape) and case Square: This is validating the shape is of type square.
  • Switch(shape) and case Square s: If the test passed then assigning shape to s. Which can be used in code then.
switch (shape)
{
case Square s:
return s.Side * s.Side;
}

Important Points:

  • Before this improvement switch statements only used to support constant patterns in which you could only use numeric and string types. Now with the ‘switch‘ pattern matching enhancement in C# 8.0, this restriction is gone, now ‘switch’ allows type pattern. so you can use any type of object with ‘switch’.
  • The case expressions in ‘switch’ are no longer limited to constant values.
  • Before this improvement, the order of your case statements was not considered important. But now with the new feature order of case statements matters as it is executed one by one. If nothing matches then go to the default case which is always the last case.

2.1 A ‘when’ clauses in switch case expressions

You can further refine your cases by using the ‘when‘ clause and add additional conditions to the case statements like:

This code is showing how we can further add the when clause to the above switch examples. After assigning values to ‘s’ or ‘c’, we can add further condition using ‘when‘ and evaluate the case on a condition.

switch (shape)
{
case Square sqr when sqr.Side == 0:
case Circle c when c.Radius == 0:
return 0;
}

How does it work:

  • Switch(shape) and case Square: This is validating the shape is of type square.
  • Switch(shape) and case Square sqr: If the test passed then assigning shape to sqr. Which can be used in code then.
  • when sqr.side == 0: using the sqr variable and also validation additional condition to execute this case or not.

2.2 ‘null’ with switch case expression

As switch supports ‘reference type’ matching also so how can we forget ‘null’?.Let’s understand with the below example:

switch (shape)
{
case null: // Explicit case for null when using a reference object in switch.
throw new ArgumentNullException();
default: // Normal default case handling
throw new ArgumentException();
}

2.3 ‘var’ declarations in switch case expressions

Lets understand this with below example

string shapeDes = " ";
switch (shapeDes)
{
case 'circle':
return 'circle';
case 'traingle':
return 'traingle';
case var objs when (objs?.Trim().Length ?? 0) == 0: // Checking white spaces
return null;
}

The var case here may match with null, the empty string, or any string that contains only white space. Also, see we are using the?. operator to verify null.

Important thing:

  • The type of var ‘objs‘ will always be matching with the type of shapeDes which is a string.
  • var ‘objs’ may be null so we will need to make sure it’s not null.

We are almost done with making our understanding about Pattern matching which was introduced in C# 7.0, so let’s see the new improvements in C# 8 to the existing Pattern matching feature of C#.


Switch Expression Pattern Matching in C# 8.0:

In C#8.0 there are many syntax changes from C# 7.0:

  • The variable comes before the switch keyword.
  • The case and: elements are replaced with =>.
  • The default case is replaced with a _ discard.
  • The bodies of each case are expressions, not statements.

The first three changes are self-explanatory from the comments.

Let’s Understand more details with an example below:

colorBand switch   // The variable is moved before the 'switch' keyword
{
    Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),    // case and : elements of syntax are gone, replaced with =>
    Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
    Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
    _              => throw new ArgumentException(),   // default 'keyword' is replaced with '_' discard
}


Let us understand the 4th change from the code sample below:

In C# 8.0 , this code

colorBand switch
{
Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
}

is replacement for code which was used before C# 8.0

switch(colorBrand)
{
case Rainbow.Orange:
return new RGBColor(0xFF, 0x7F, 0x00);
break;
}

Property Patterns in C# 8.0

The property pattern enables you to match on properties of the object examined.

For Example:
we have a class object ‘Country’ with one of its property ‘State‘, location is an object of type Country.

location switch
{
{ State: "NY" } => Population * 200, // We are matching here on property
{ State: "MN" } => Population * 70,
{ State: "NJ" } => Population * 50,
_ => 0M
};

Tuple Pattern

Tuple patterns allow you to implement ‘switch’ statements based on multiple values expressed as a tuple.

Let us understand this with the below Example:
We have a tuple with two fields ‘firstname‘ and ‘lastname

(firstname, lastname) switch
{
("Sachin", "Tendulkar") => "The Little Master",
("Rahul", "Dravid") => "The wall",
("Saurav", "Ganguly") => "The Prince of Calcutta",
(, ) => " Team India"
};

Positional Patterns

A positional pattern checks that the input object value:

  1. is not null.
  2. Invokes an appropriate Deconstruct method on that object and get the de-constructed property values.
  3. Performs further pattern matching on property values.

The syntax is tuple-like pattern syntax.

Let us understand with Example:

  1. We have a Class Point with two properties ‘X‘ & ‘Y‘.
  2. We also have a de-constructor function in this class which provides us the values of these properties.
public void Deconstruct(out int x, out int y) =>
(x, y) = (X, Y);

So now let us use the Positional pattern:

point switch // point is object of class Point
{
var (x, y) when x > 0 && y > 0 => 'Quadrant One', // First var(x,y) will check point is not null
// the will call Deconstruct function on object point to get values of x and y
// then do the when clause
// if matching then execute expression
var (x, y) when x < 0 && y > 0 => 'Quadrant Two',
var (, ) => 'Quadrant OnBorder',
_ => 'Quadrant Unknown'
};

Important Points:

  • The object must provide a deconstructor.
  • This is a recursive pattern, which means it contains nested patterns.