Learn About Pattern Matching In C#

Introduction

 
In this article, we are going to discuss pattern matching, introduced in C# 7.0 and improved in C#8.0 and C# 9.0. This article can be very useful for beginners, intermediates, and professionals.
 

What is Pattern Matching?

 
A question that comes to mind before we start with pattern matching is: 
 
What are we using for matching data in C#? The answer is,  If/else condition or switch/case statement.
 
If/else and switch/case condition have the limitation of comparing a defined member variable with primitive data types only and that creates the requirement of Pattern matching.
 

Pattern matching in C# 7.0

 
C# 7.0 introduces the wonderful feature “Pattern matching” with the below case.
  • If/else
  • Switch/Case
Let’s use the below code to understand pattern matching in C# 7.0.
 
Before C# 7.0, if we wanted to calculate the area of a Rectangle and Circle, we had to write the below code.
  1. using System;  
  2.   
  3. namespace PatternMatching  
  4. {  
  5.     class Program  
  6.     {  
  7.         static void Main(string[] args)  
  8.         {  
  9.             Rectangular r1 = new Rectangular { Width = 12.34, Height = 56.12 };  
  10.             Rectangular r2 = new Rectangular { Width = 56.34, Height = 56.34 };  
  11.   
  12.             Circle c1 = new Circle { Redius = 34.56 };  
  13.   
  14.             CalculateArea(r1);  
  15.             CalculateArea(r2);  
  16.             CalculateArea(c1);  
  17.   
  18.             Console.ReadLine();  
  19.         }  
  20.   
  21.         public static void CalculateArea(Figure f)  
  22.         {  
  23.             if(f is Rectangular)  
  24.             {  
  25.                 // Perform Conversion as parent class can not access member of child class directly.  
  26.                 Rectangular rec = f as Rectangular;  
  27.                 Console.WriteLine("Area of Rectangular {0}", rec.Width * rec.Height);  
  28.             }  
  29.             else if(f is Circle)  
  30.             {  
  31.                 Circle Cir = f as Circle;  
  32.                 Console.WriteLine("Area of Circle {0}", Circle.pi * Cir.Redius * Cir.Redius);  
  33.             }  
  34.         }  
  35.     }  
  36.     public class Figure  
  37.     {  
  38.         public const float pi = 3.14f;  
  39.     }  
  40.     public class Circle : Figure  
  41.     {  
  42.         public double Redius { getset; }  
  43.     }  
  44.     public class: Figure  
  45.     {  
  46.         public double Width { getset; }  
  47.         public double Height { getset; }  
  48.     }  
  49. }  
Output
 
In the above code, we have created three classes
  • Figure – Parent Class
  • Rectangular – Child class inherited from parent class Figure
  • Circle – Child class inherited from parent class Figure
We have method class, CalculateArea, which is used to calculate the area of a Rectangle or Circle.
 
As rectangular and circle have different formulates for calculating area. Hence, we have to follow the below steps to calculate the area in before C# 7.0, 
  1. if condition to know figure “is” Rectangular or Circle
  2. Then we have to use “as” for explicit conversation.
  3. Calculate and CalculateArea.
Now we will write the same code in C# 7.0. 
  1. public static void CalculateArea(Figure f) {  
  2.     if (f is Rectangular rec) Console.WriteLine("Area of Rectangular {0}", rec.Width * rec.Height);  
  3.     else if (f is Circle Cir) Console.WriteLine("Area of Circle {0}", Circle.pi * Cir.Redius * Cir.Redius);  
  4. }   
The above code is an example of Pattern Matching in C#7.0. In the code, we have removed explicit class conversion code which makes the code simpler and clearer.
 

Switch/Case -Pattern matching

 
Same code with Switch/case in C# 7.0
  1. public static void CalculateArea(Figure f) {  
  2.     switch (f) {  
  3.         case Rectangular rec:  
  4.             Console.WriteLine("Area of Rectangular {0}", rec.Width * rec.Height);  
  5.             break;  
  6.         case Circle Cir:  
  7.             Console.WriteLine("Area of Circle {0}", Circle.pi * Cir.Redius * Cir.Redius);  
  8.             break;  
  9.     }  
  10. }   
You might notice that in the above code that we do not need comparison using the “is” keyword and conversion using the “as” keyword.
 
Let's discuss the below line of code, 
  1. Rectangular r2 = new Rectangular { Width = 56.34, Height = 56.34 };  
Width and Height are the same, which means this is square and not rectangular. Let's implement square logic now,
  1. public static void CalculateArea(Figure f) {  
  2.     switch (f) {  
  3.         case Rectangular rec when rec.Width == rec.Height:  
  4.             Console.WriteLine("Area of Rectangular {0}", rec.Width * rec.Height);  
  5.             break;  
  6.         case Rectangular rec:  
  7.             Console.WriteLine("Area of Rectangular {0}", rec.Width * rec.Height);  
  8.             break;  
  9.         case Circle Cir:  
  10.             Console.WriteLine("Area of Circle {0}", Circle.pi * Cir.Redius * Cir.Redius);  
  11.             break;  
  12.     }  
  13. }   
In the first case statement, we are doing a comparison using when. This is one of the powerful features of pattern matching.
 

Pattern Matching in C# 8.0

 
In C# 8.0, switch expression is introduced. It is not a Switch case but a switch expression.
 
The main difference between the Switch case and Switch Expression is, a switch expression is a way to return a specific value based on another value.
 
Switch expression can be used with Tuple. If you want to learn about tuple please go through the article.
 
We will try to implement the same example using C# 8.0 
  1. public static void CalculateArea(Figure f) {  
  2.     var value = f  
  3.     switch {  
  4.         Rectangular rec_ when rec_.Width == rec_.Height => rec_.Width * rec_.Height;  
  5.         Rectangular rec_ => rec_.Width * rec_.Height,  
  6.             Circle cir_ => Circle.pi * cir_.Redius * cir_.Redius,  
  7.             _ =>  
  8.             throw new NotImplementedException()  
  9.     };  
  10.     Console.WriteLine("Areaf  {0}", value);  
  11. }  
The switch expression doesn’t have a case statement. It is easy to write compare to the switch case.
 

Pattern Matching in C# 9.0

 
Relational patterns and Logical pattern combinators were introduced with C# 9.0, and you can use them not only with is expressions but also in switch expressions. Also, it has improved in Type Pattern matching.
 
We will start with improvement in Type Pattern matching.
 
Type pattern
 
See the below code, 
  1. public static bool IsCircle(Figure f) => f  
  2. switch {  
  3.     Circle _ => true,  
  4.         Rectangular _ => false,  
  5.         _ =>  
  6.         throw new NotImplementedException()  
  7. };   
The above function will return true if the figure is a circle and false in the case of a rectangle. 
 
Let’s observe the below line of code, 
  1. Circle _ => true,  
We need _ in the case where we are not using an object at all. Underscore (_) is required in c# 8.0 but not required in c# 9.0.
 
There is no big change in functionality, but it is a very important code, cleaning-wise.
 
Same code in C# 9.0,
  1. public static bool IsCircle(Figure f) => f  
  2. switch {  
  3.     Circle => true,  
  4.         Rectangular => false,  
  5.         _ =>  
  6.         throw new NotImplementedException()  
  7. };   

Relation Pattern Matching

 
To understand the Relation pattern, consider that we have a member class and we need to perform some calculation based on Member age.
  1. using System;  
  2. namespace PatternMatching {  
  3.     class Program {  
  4.         static void Main(string[] args) {  
  5.             var member = new Member {  
  6.                 Age = 45  
  7.             };  
  8.             Console.WriteLine("Charges : {0}", ChargesCalculate(member));  
  9.             Console.ReadLine();  
  10.         }  
  11.         static double ChargesCalculate(Member m) => m.Age  
  12.         switch {  
  13.             >= 25 => 350, < 25 => 350,  
  14.         };  
  15.         public class Member {  
  16.             public int Age {  
  17.                 get;  
  18.                 set;  
  19.             }  
  20.         }  
  21.     }  
  22. }   
ChangesCalculate method is calculated changes based on age. If age is greater or equal to 25 then changes would be 350 else 250. This method can be worked in only .Net (C# 9.0), and not working in C# 8.0. This is something new introduced in C# 9.0.
 
That’s all for this article. Hope you enjoy this article.