Pattern Matching in C#

Pattern Matching in C# with Examples

In this article, I am going to discuss the Pattern Matching in C# with some examples. Please read our previous article where we discussed the improvement of Out Variables in C# with examples. The Pattern Matching is a new feature which was introduced in C# 7.0. As part of this article, we are going to discuss the following pointers.

  1. What is Pattern Matching in C#?
  2. How to implement Pattern Matching?
  3. How to implement Pattern Matching Before C# 7?
  4. Pattern Matching Using is Expression.
  5. How to Implement Pattern Matching using a switch statement?
  6. Understanding the use of  When clauses in the case statement in C#
What is Pattern Matching in C#?

The Pattern Matching is a mechanism which tests a value i.e. whether the value has a specific shape or not. It the value is in a specific shape then it will extract the data from the value. If this is not clear at the moment, then don’t worry we will understand this with multiple examples.

How to implement Pattern Matching in C#?

To implement Pattern Matching, we are provided with two language constructs such as:

  1. Pattern Matching using the “is” expression
  2. The Pattern Matching using the “case” statements

In the upcoming versions of C#, we may expect more pattern matching expressions. The Pattern Matching is useful in many ways however C# 7.0 currently supports the following.

  1. It can be used with any data type including the custom data types whereas if/else can only be used with primitive types.
  2. The Pattern matching has the ability to extract the data from the expression.
Pattern Matching in C# with “is” expression:

The “is” operator is available from the first version of C# and it is used to check whether an object is compatible with a specific type or not. For example, if a specific interface is implemented, or if the type of the object derives from a base class or not. The result of this operator is true or false. Let us understand this with an example.

First create four classes such as Shape, Circle, Rectangle, and Tringle as shown below.

using System;
namespace PatternMatchingDemo
{
    public class Shape
    {
        public const float PI = 3.14f;
    }
    public class Circle : Shape
    {
        public double Radius { get; }
        public Circle(double radius)
        {
            Radius = radius;
        }
    }
    public class Rectangle : Shape
    {
        public double Length { get; }
        public double Height { get; }
        public Rectangle(double length, double height)
        {
            Length = length;
            Height = height;
        }
    }
    public class Triangle : Shape
    {
        public double Base { get; }
        public double Height { get; }
        public Triangle(double @base, double height)
        {
            Base = @base;
            Height = height;
        }
    }
}

The above code is straight forward. Here, we have created one base class i.e. Shape and three derived classes (Rectangle, Circle and Triangle). 

Pattern Matching Before C# 7:

Let us first understand our requirement. We need to create a method with one parameter of type Shape. The reason is, the Shape class is the base class and it can hold the object reference of any of its child classes like Rectangle, Triangle, and Circle. So, modify the Program class as shown below.

using System;
namespace PatternMatchingDemo
{
    class Program
    {
        static void Main()
        {
            Circle circle = new Circle(10);
            DisplayArea(circle);
            Rectangle rectangle = new Rectangle(10, 5);
            DisplayArea(rectangle);
            Triangle triangle = new Triangle(10, 5);
            DisplayArea(triangle);
            Console.ReadKey();
        }
        public static void DisplayArea(Shape shape)
        {
             if (shape is Circle)
            {
                Circle c = (Circle)shape;
                Console.WriteLine("Area of Circle is : " + c.Radius * c.Radius * Shape.PI);
            }
            else if (shape is Rectangle)
            {
                Rectangle r = (Rectangle)shape;
                Console.WriteLine("Area of Rectangle is : " + r.Length * r.Height);
            }
            else if (shape is Triangle)
            {
                Triangle t = (Triangle)shape;
                Console.WriteLine("Area of Triangle is : " + 0.5 * t.Base * t.Height);
            }
            else
            {
                throw new ArgumentException(message: "Invalid Shape", paramName: nameof(shape));
            }
        }
    }
}

Please have a look at the DisplayArea() method. Here in the DisplayArea() method, we are testing each type in a series of “if” and “is” statements and then we are explicitly casting the type to a specific type and then doing some action. Now, let us understand how to use the new Pattern Matching Mechanism which was introduced in C# 7.0.

Pattern Matching Using is Expression:

We can simplify the previous example by using the “is” expression pattern which will check and assign the value to a variable. So, in order to do this, please modify the DisplayArea() method of the Program class as shown below.

public static void DisplayArea(Shape shape)
{
    if (shape is Circle c)
    {
        Console.WriteLine("Area of Circle is : " + c.Radius * c.Radius * Shape.PI);
    }
    else if (shape is Rectangle r)
    {
        Console.WriteLine("Area of Rectangle is : " + r.Length * r.Height);
    }
    else if (shape is Triangle t)
    {
        Console.WriteLine("Area of Triangle is : " + 0.5 * t.Base * t.Height);
    }
    else
    {
        throw new ArgumentException(message: "Invalid Shape", paramName: nameof(shape));
    }
}

In the above example, we are using the “is” expressions which will test the variable type and if it matches to the type then it assigns that value to the variable. For better understanding please have a look at the following image.

Pattern Matching in C#

Pattern Matching using the “switch” statement in C#:

The traditional switch statement in C# is also a pattern-matching expression. So, let us see how to use the switch statement to implement the previous example. Modify the DisplayArea method as shown below to implement Pattern Matching using a switch statement in C#.

public static void DisplayArea(Shape shape)
{
    switch (shape)
    {
        case Circle c:
            Console.WriteLine("Area of Circle is : " + c.Radius * c.Radius * Shape.PI);
            break;
        case Rectangle r:
            Console.WriteLine("Area of Rectangle is : " + r.Length * r.Height);
            break;
        case Triangle t:
            Console.WriteLine("Area of Triangle is : " + 0.5 * t.Base * t.Height);
            break;
        default:
            throw new ArgumentException(message: "Invalid Shape", paramName: nameof(shape));
        case null:
            throw new ArgumentNullException(nameof(shape));
    }
}
Points to Remember while working with Case Statement in C#:

You need to remember the following points while working with the newly extended switch statement for Pattern Matching.

The default clause is always evaluated last: In our example, the null case statement comes at the last but it will be checked before the default case statement is checked. The reason for this is for the compatibility with the existing switch statements. So it is always advisable and a good programming practice to put the default statement at the end.

The order of case clauses is now mattered: Just like the catch clauses in the try block, the first one that matches in the case statement gets picked. So as a developer it is important to write the case statement in the proper order. 

Case Expressions using When clauses in C#:

Let us understand the use of case Expression using when clause with an example. In our example, when the length and height both are same for the rectange then we need to treat it as a Square and display the message accordingly. We can specify this condition using the when clause. So, modify the main method and DisplayArea method of the Program class as shown below. 

namespace PatternMatchingDemo
{
    class Program
    {
        static void Main()
        {
            Rectangle square = new Rectangle(10, 10);
            DisplayArea(square);
            Rectangle rectangle = new Rectangle(10, 5);
            DisplayArea(rectangle);
            Circle circle = new Circle(10);
            DisplayArea(circle);
            Triangle triangle = new Triangle(10, 5);
            DisplayArea(triangle);
            Console.ReadKey();
        }
        public static void DisplayArea(Shape shape)
        {
            switch (shape)
            {
                case Rectangle r when r.Length == r.Height:
                    Console.WriteLine("Area of Sqaure is : " + r.Length * r.Height);
                    break;
                case Rectangle r:
                    Console.WriteLine("Area of Rectangle is : " + r.Length * r.Height);
                    break;
                case Circle c:
                    Console.WriteLine("Area of Circle is : " + c.Radius * c.Radius * Shape.PI);
                    break;
                case Triangle t:
                    Console.WriteLine("Area of Triangle is : " + 0.5 * t.Base * t.Height);
                    break;
                default:
                    throw new ArgumentException(message: "Invalid Shape",paramName: nameof(shape));                
            }
        }
    }
}

Now, run the application and you should the output as shown below.

Patterning Matching in C# with Case Stamenet

The most important point that you need to remember is, you need to place the case statement with when clause first then the normal case statement for the same type. Please have a look at the following diagram for better understanding.

Case Statement Order when using When clause in C# Pattern Matching

In the next article, I am going to discuss the Digit separators in C# with some examples. Here, in this article, I try to explain the Pattern Matching in C# using both “is” and “case” expression step by step with some simple examples.

Leave a Reply

Your email address will not be published. Required fields are marked *