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 before proceeding to this article where we discussed the power of new Out Variables in C# with examples. The Pattern Matching is a new feature which was introduced in C# 7.0, 

The Pattern Matching in C# is nothing but 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 examples.

Types of Pattern Matching:

There are there types of Pattern Matching in C# 7, they are as follows

  1. Constant patterns: It is of the form c where c is a constant expression, which will tests that the input value is equal to c.
  2. Type patterns: It is of the form T x where T is a type and x is an identifier, which will tests that the input value has the type T, and if so, then extracts the values into a new variable of the type T.
  3. Var patterns: It is of the form var x where var is the anonymous type and x is an identifier, which will always test and simply put the value of the input into a fresh variable x with the same type as the input.
How to implement Pattern Matching in C#?

To implement Pattern Matching, we have provided 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 C# 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.

Let us understand Pattern Matching with examples.

Pattern Matching in C# with “is” expression:

The “is” operator is available from the first version of C#. This “is” operator is used to checking if 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.

From C# 7.0, the “is” operator now can also be used to check for the patterns. The “is” expression checks if an object is compatible with a given type or not.

The following example defines five classes, Square, Circle, Rectangle, Tringle and Program.

public class Square
{
    public double Side { get; }

    public Square(double side)
    {
        Side = side;
    }
}
public class Circle
{
    public double Radius { get; }

    public Circle(double radius)
    {
        Radius = radius;
    }
}
public class Rectangle
{
    public double Length { get; }
    public double Height { get; }

    public Rectangle(double length, double height)
    {
        Length = length;
        Height = height;
    }
}
public class Triangle
{
    public double Base { get; }
    public double Height { get; }

    public Triangle(double @base, double height)
    {
        Base = @base;
        Height = height;
    }
}
class Program
{
    static void Main()
    {
        Square square = new Square(10);
        Console.WriteLine("Area of square is : " + ComputeArea(square));

        Rectangle rectangle = new Rectangle(10, 5);
        Console.WriteLine("Area of square is : " + ComputeArea(rectangle));

        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }

    public static double ComputeArea(object shape)
    {
        if (shape is Square)
        {
            var s = (Square)shape;
            return s.Side * s.Side;
        }
        else if (shape is Circle)
        {
            var c = (Circle)shape;
            return c.Radius * c.Radius * Math.PI;
        }
        else if (shape is Rectangle)
        {
            var r = (Rectangle)shape;
            return r.Length * r.Height;
        }
        else if (shape is Triangle)
        {
            var t = (Triangle)shape;
            return 0.5 * t.Base * t.Height;
        }
        else
        {
            throw new ArgumentException(
           message: "shape is not a recognized shape",
           paramName: nameof(shape));
        }
    }
}

This is how we implement before C# 7. Before C# 7.0 we need to test each type in a series of “if” and “is” statements as shown in the above ComputeArea() method. The above example is of “type” pattern. Here we are testing the variable to determine its type and then based on the type we doing some action.

Using is Pattern:

We can simplify the previous example by using the “is” expression pattern which will check and assign the value to a variable as shown below.

public static double ComputeArea(object shape)
{
    if (shape is Square s)
    {
        return s.Side * s.Side;
    }
    else if (shape is Circle c)
    {
        return c.Radius * c.Radius * Math.PI;
    }
    else if (shape is Rectangle r)
    {
        return r.Length * r.Height;
    }
    else if (shape is Triangle t)
    {
        return 0.5 * t.Base * t.Height;
    }
    else
    {
        throw new ArgumentException(
       message: "shape is not a recognized 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 proper type.

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

The traditional switch statement in C# is also a pattern matching expression. The switch statement supports the constant pattern. That means we can compare a variable to any constant used in the switch statements.

The most important point that you need to remember is, the constant pattern is the only pattern which is supported by the switch statement. That is too, you can use only numeric and string type constants. Now, the restrictions have been removed and you can write the switch statement also using the “type” pattern.

Modify the compute area method as shown below to implement Pattern Matching using a switch statement in C#.
public static double ComputeArea(object shape)
{
    switch (shape)
    {
        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Rectangle r:
            return r.Length * r.Height;
        case Triangle t:
            return 0.5 * t.Base * t.Height;
        default:
            throw new ArgumentException(
       message: "shape is not a recognized shape",
       paramName: nameof(shape));

  case  null:
             throw new ArgumentNullException(nameof(shape));

    }
}
Points to Remember:

There are several points to remember while working with the newly extended switch statement.

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. For example, the square case should come before the rectangle case. 

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.

Case Expressions using When clauses:

Let say, we want to make some special cases when the value of the area is 0. We can write special cases here using the when clause. A square shape with 0 side length or a circle with a radius as 0 has always going to be 0 areas. We can specify those condition using the when clause:

Let’s modify the main method and ComputeArea method to understand this concept.

class Program
{
    static void Main()
    {
        Square square = new Square(0);
        Console.WriteLine("Area of square is : " + ComputeArea(square));

        Circle circle = new Circle(0);
        Console.WriteLine("Area of Circle is : " + ComputeArea(circle));

        Rectangle rectangle = new Rectangle(10, 5);
        Console.WriteLine("Area of square is : " + ComputeArea(rectangle));

        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }

    public static double ComputeArea(object shape)
    {
        switch (shape)
        {
            case Square s when s.Side == 0:
            case Circle c when c.Radius == 0:
                return 0;

            case Square s:
                return s.Side * s.Side;
            case Circle c:
                return c.Radius * c.Radius * Math.PI;
            case Rectangle r:
                return r.Length * r.Height;
            case Triangle t:
                return 0.5 * t.Base * t.Height;

            default:
                throw new ArgumentException(
                    message: "shape is not a recognized shape",
                    paramName: nameof(shape));
        }
    }
}

Here you need to remember some points. Now it is possible to use multiple case statements for one switch section. The statement block is going to be executed when any of the case labels are true. For example, in our case, if the switch expression is either a square or circle with 0 areas, then the method is going to returns the constant 0.

In the next article, I am going to discuss the Digit separators in C# with some examples.

SUMMARY

In this article, I try to explain the Pattern Matching step by step with some simple examples. I hope you understood the use and need of C# Pattern Matching with examples.

Leave a Reply

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