Pattern Matching in C#

06 Oct, 2021
Pattern matching is a feature that allows testing an expression for the occurrence of a given pattern. It is a feature more prevalent in functional languages. Pattern matching is Boolean in nature, which implies there are two possible outcomes: either the expression matches the pattern or it does not. This feature was first introduced in C# 7.0 and has then undergone a series of improvements in successive versions of the language. 
Pattern matching allows operations like:
type checking(type pattern)
null checking(constant pattern)
comparisons(relational pattern)
checking and comparing values of properties (property pattern)
object deconstruction(positional pattern),
expression reuse using variable creation(var pattern)
to be expressed by using minimal and succinct syntax. Moreover, these patterns can be nested and can comprise several sub-patterns. Patterns can also be combined using pattern combinators(and, or and not). C# allows pattern matching through three constructs:
1. is operator 
Before C# 7.0, the only purpose of the is operator was to check if an object is compatible with a specific type. Since C# 7.0, the is operator has been extended to test if an expression matches a pattern.
Syntax:
expression is pattern
2. switch statements
Just like how a switch statement can be used to execute a branch of code(case) by testing an expression for a set of values, it can also be used to execute a branch of code by testing an expression for the occurrence of a set of patterns.
Syntax:
switch (expression)
{
    case pattern1:
    // code to be executed
    // if expression matches pattern1
    break;
    case pattern2:
    // code to be executed
    // if expression matches pattern2
    break;
    ...
    case patternN:
    // code to be executed
    // if expression matches patternN
    break;
    default:
    // code to be executed if expression
    // does not match any of the above patterns  
}
3. switch expressions 
A set of patterns can also be tested using a switch expression to select a value based on whether the pattern is matched.
Syntax:
expression switch
{
    pattern1 => value1,
    pattern2 => value2,
    ...
    patternN => valueN,
    _ => defaultValue
}
As of C# 9, the following patterns are supported by the language. 
Type Pattern
Relational Pattern
Property Pattern
Positional Pattern
var Pattern
Constant Pattern
C# also supports the use of the following constructs with pattern matching:
Variable Declarations
Pattern Combinators (and, or and not)
Discard Variables (_)
Nested Patterns or Sub-patterns
The type pattern can be used to check if the runtime type of an expression matches the specified type or is compatible with that type. If the expression, that is, the value that is being matched is compatible with the type specified in the pattern, the match succeeds. The type pattern can optionally also contain a variable declaration. If the value that is being tested matches the type, then it will be cast to this type and then assigned to this variable. Variable declarations in patterns are described further.
Syntax:
// Used in C# 9 and above
TypeName 
// Used in C# 7
TypeName variable
TypeName _
Example:
C#
// C# program to illustrate the concept of Type Patternusing System; public class GFG{     static void PrintUppercaseIfString(object arg){    // If arg is a string:    // convert it to a string    // and assign it to variable message    if (arg is string message)    {        Console.WriteLine($"{message.ToUpper()}");    }    else    {        Console.WriteLine($"{arg} is not a string");    }} // Driver codestatic public void Main(){    string str = "Geeks For Geeks";    int number = 42;    object o1 = str;    object o2 = number;     PrintUppercaseIfString(o1);    PrintUppercaseIfString(o2);}}
  
GEEKS FOR GEEKS
42 is not a string
In the example above, the PrintUppercaseIfString() method accepts an argument of type object called arg. Any type in C# can be up cast to object because, in C#, all types derive from object. This is called Type Unification.
 
Automatic Casting
If arg is a string, it will be downcast from object to string and will be assigned to a variable called message. If arg is not a string but a different type, the else block will be executed. Therefore, both the type checking and the cast are combined in one expression. If the type does not match, the variable will not be created. 
The type pattern used with a switch statement can help to select a branch of code (case branch) depending on the type of value. The code below defines a method called PrintType() which accepts an argument as an object and then prints different messages for different types of arguments:
 
C#
// C# program to illustrate the concept of Type Pattern Switchusing static System.Console; // Allows using WriteLine without Console. prefixpublic class Person{    public string Name    {        get;        set;    }} class GFG{     static void PrintType(object obj){    switch (obj)    {      case Person p:          WriteLine("obj is a Person");          WriteLine($"Name of the person: {p.Name}");          break;       case int i:          WriteLine("obj is an int");          WriteLine($"Value of the int: {i}");          break;       case double d:          WriteLine("obj is a double");          WriteLine($"Value of the double: {d}");          break;       default:          WriteLine("obj is some other type");          break;    }     WriteLine(); // New line} // Driver codestatic public void Main(){    var person = new Person { Name = "Geek" };     PrintType(42);    PrintType(person);    PrintType(3.14);    PrintType("Hello");}}
  
Output:
 
obj is an int
Value of the int: 42

obj is a Person
Name of the person: Geek

obj is a double
Value of the double: 3.14

obj is some other type
Relational patterns were introduced in C# 9. They help us perform comparisons on a value using the: <(less than), <=(less than or equal to), >(greater than), and >=(greater than or equal to) operators.
 
Syntax:
< constant
<= constant
> constant
>= constant
Example:
C#
// Program to check if a number is positive,// negative or zero using relational patterns// using a switch statementusing System; class GFG{     public static string GetNumberSign(int number){    switch (number)    {        case < 0:            return "Negative";        case 0:            return "Zero";        case >= 1:            return "Positive";    }} // Driver codestatic public void Main(){    int n1 = 0;    int n2 = -31;    int n3 = 18;         Console.WriteLine(GetNumberSign(n1));    Console.WriteLine(GetNumberSign(n2));    Console.WriteLine(GetNumberSign(n3));}}
Output:
Zero
Negative
Positive
The above example can be written more concisely using a switch expression:
C#
// Program to check if a number// is positive, negative or zero// using relational patterns// with a switch expressionusing System; class GFG{     public static string GetNumberSign(int number){    return number switch    {        < 0 => "Negative",        0 => "Zero",        >= -1 => "Positive"    };} // Driver codestatic public void Main(){    int n1 = 0;    int n2 = -31;    int n3 = 18;         Console.WriteLine(GetNumberSign(n1));    Console.WriteLine(GetNumberSign(n2));    Console.WriteLine(GetNumberSign(n3));}}
Output:
Zero
Negative
Positive
Similarly, relational patterns can also be used with the is operator:
int n = 2;
Console.WriteLine(n is <= 10); // Prints true
Console.WriteLine(n is > 5); // Prints false
This may not be as useful on its own because n is <= 10 is the same as writing n <= 10. However, this syntax will be more convenient with pattern combinators(discussed further).   
Property patterns allow matching values of properties defined on an object. The pattern specifies the name of the property to be matched and then after a colon(:) the value that must match. Multiple properties and their values can be specified by separating them with commas.
 
Syntax:
 
{ Property1: value1, Property2 : value2, ..., PropertyN: valueN }
Such syntax allows us to write:
 
“Geeks” is { Length: 4 }
Instead of:
 
“Geeks”.Length == 4
Example:
 
C#
// C# program to illustrate the concept of Property Patternusing System; class GFG{ public static void DescribeStringLength(string str){         // Constant pattern, discussed further    if (str is null)    {        Console.WriteLine("Null string");    }     if (str is { Length: 0 })    {        Console.WriteLine("Empty string");        return;    }     if (str is { Length: 1 })    {        Console.WriteLine("String of length 1");        return;    }     Console.WriteLine("Length greater than 1");    return;} // Driver codestatic public void Main(){    DescribeStringLength("Hello!");    Console.WriteLine();    DescribeStringLength("");    Console.WriteLine();    DescribeStringLength("X");    Console.WriteLine();}}
  
Output:
 
Length greater than 1
Empty string
String of length 1
Positional patterns allow specifying a set of values in parentheses and will match if each value in the parentheses matches the values of the matched object. The object values are extracted through deconstruction. Positional patterns are based on the deconstruction pattern. The following types can use positional patterns:
Any type with one or more deconstructors. A type is said to have a deconstructor if it defines one or more Deconstruct() methods that accept one or more out parameters. The Deconstruct() method can also be defined as an extension method.
Tuple types(instances of System.ValueTuple).
Positional record types. (since C# 9).
Syntax:
 
(constant1, constant2, ...)
Example 1: Positional Pattern with a type that defines a Deconstruct() method
The code below defines two functions LogicalAnd() and LogicalOr(), both of which accept an object of BooleanInput. BooleanInput is a value-type(struct) that represent two Boolean input values. The methods use both these input values and perform a Logical AND and Logical OR operation on these values. C# already has logical AND(&&) and logical OR(||) operators which perform these operations for us. However, the methods in this example implement these operations manually to demonstrate positional patterns.
C#
// C# program to illustrate the concept of Positional Patternusing System; // Represents two inputs to the truth tablepublic struct BooleanInput{    public bool Input1    {        get;        set;    }     public bool Input2    {        get;        set;    }     public void Deconstruct(out bool input1,                            out bool input2)    {        input1 = Input1;        input2 = Input2;    }} class GFG{     // Performs logical AND on an input objectpublic static bool LogicalAnd(BooleanInput input){         // Using switch expression    return input switch    {        (false, false) => false,        (true, false) => false,        (false, true) => false,        (true, true) => true    };} // Performs logical OR on an input objectpublic static bool LogicalOr(BooleanInput input){         // Using switch statement    switch (input)    {        case (false, false):            return false;        case (true, false):            return true;        case (false, true):            return true;        case (true, true):            return true;    }} // Driver codestatic public void Main(){    var a = new BooleanInput{Input1 = true,                             Input2 = false};    var b = new BooleanInput{Input1 = true,                             Input2 = true};       Console.WriteLine("Logical AND:");    Console.WriteLine(LogicalAnd(a));    Console.WriteLine(LogicalAnd(b));       Console.WriteLine("Logical OR:");    Console.WriteLine(LogicalOr(a));    Console.WriteLine(LogicalOr(b));}}
  Output: 
Logical AND:
False
True
Logical OR:
True
True
Example 2: Using positional patterns with tuples
Any instance of System.ValueTuple can be used in positional patterns. C# provides a shorthand syntax for creating tuples using parentheses:(). A tuple can be created quickly on the fly by wrapping a set of already declared variables in parentheses. In the following example, the LocatePoint() method accepts two parameters representing the x and y coordinates of a point then creates a tuple after the switch keyword using an additional pair of parentheses. The outer parentheses are part of the switch statement syntax, the inner parentheses create the tuple using the x and y variables.
C#
// C# program to illustrate the concept of Positional Patternusing System; class GFG{     // Displays the location of a point// by accepting its x and y coordinatespublic static void LocatePoint(int x, int y){    Console.WriteLine($"Point ({x}, {y}):");         // Using switch statement    // Note the double parantheses    switch ((x, y))    {        case (0, 0):            Console.WriteLine("Point at origin");            break;        case (0, _): // _ will match all values for y            Console.WriteLine("Point on Y axis");            break;        case (_, 0):            Console.WriteLine("Point on X axis");            break;        default:            Console.WriteLine("Point elsewhere");            break;    }} // Driver codestatic public void Main(){    LocatePoint(10, 20);    LocatePoint(10, 0);    LocatePoint(0, 20);    LocatePoint(0, 0);}}
  Output: 
Point (10, 20):
Point elsewhere
Point (10, 0):
Point on X axis
Point (0, 20):
Point on Y axis
Point (0, 0):
Point at origin
The constant pattern is the simplest form of a pattern. It consists of a constant value. It is checked whether the expression that is being matched is equal to this constant. The constant can be:
A numeric, Boolean, character, or string literal.
An enum value
null.
A const field.
The constant pattern usually appears as part of other patterns as a sub-pattern(discussed further) but it can also be used on its own.
Some examples of the constant pattern being used with the is operator would be:  
expression is 2 // int literal
expression is "Geeks" // string literal
expression is System.DayOfWeek.Monday // enum
expression is null  // null
Notice the final example, where the pattern is null. This implies that pattern matching provides another way to check if an object is null. Also, expression is null may be more readable and intuitive than the typical expression == null.
In the context of a switch statement, the constant pattern looks identical to a regular switch statement without pattern matching.
Example: The following example uses a switch expression with the constant pattern in a method called DayOfTheWeek() which returns the name of the day of a week from the number passed to it. 
C#
// C# program to illustrate the concept of Constant Patternusing System; class GFG{    // Returns the name of the day of the weekpublic static string DayOfTheWeek(int day){         // Switch expression    return day switch    {        1 => "Sunday",        2 => "Monday",        3 => "Tuesday",        4 => "Wednesday",        5 => "Thursday",        6 => "Friday",        7 => "Saturday",        _ => "Invalid week day"    };} // Driver codestatic public void Main(){    Console.WriteLine(DayOfTheWeek(5));    Console.WriteLine(DayOfTheWeek(3));}}
Output:
Thursday
Tuesday
The var pattern works slightly different from other patterns. A var pattern match always succeeds, which means, the match result is always true. The purpose of the var pattern is not to test an expression for a pattern but to assign an expression to a variable. This allows reusing the variable in consecutive expressions. The var pattern is a more general form of the type pattern. However, there is no type specified; the var is used instead, so there is no type checking and the match is always successful.
Syntax: 
var varName
var (varName1, varName2, ...)
Consider the following code where a DateTime object’s day and month has to be compared:
var now = DateTime.Now;
if (now.Month > 6 && now.Day > 15)
{
    // Do Something
}
This can be written in one line using the var pattern:
if (DateTime.Now is var now && now.Month > 6 && now.Day > 15)
{
    // Do Something
}
C# 9 has also introduced pattern combinators. Pattern combinators allow combining multiple patterns together. The following are the pattern combinators: 
Negative Pattern: not
Conjunctive Pattern: and
Disjunctive Pattern or
not 2
not < 10
not null
> 0 and < 10
{ Year: 2002 } and { Month: 1 }
not int and not double
“Hi” or “Hello” or “Hey”
null or (0, 0)
{ Year: 2004 } or { Year: 2002 }
Pattern combinators are a lot like logical operators(!, &&, ||) but the operands are patterns instead of conditions or Boolean expressions. Combinators make pattern matching more flexible and also helps to save a few keystrokes.
 
Simpler Comparisons
By using pattern combinators with the relational pattern, an expression can be compared with multiple other values without repeating the expression over and over. For instance, consider the following:
int number = 42;
if (number > 10 && number < 50 && number != 35) 
{
    // Do Something
}
With pattern matching and combinators, this can be simplified:
int number = 42;
if (number is > 10 and < 50 and not 35)
{
   // Do Something
}
As observable, the variable name number need not be repeated; comparisons can be seamlessly chained.
Checking if a value is non-null
Under the constant patterns section above, an alternative way to check if a value is null was discussed. Pattern combinators provide a counterpart that allows checking if a value is not null:
if (expression is not null) 
{
}
Example: The following example defines a method called IsVowel() that checks if a character is a vowel or not using the or pattern combinator to combine multiple constant patterns:
C#
// C# program to illustrate the concept of Pattern Combinatorsusing System; class GFG{     public static bool IsVowel(char c){    return char.ToLower(c) is 'a' or 'e' or 'i' or 'o' or 'u';} // Driver codepublic static void Main(){    Console.WriteLine(IsVowel('A'));    Console.WriteLine(IsVowel('B'));    Console.WriteLine(IsVowel('e'));    Console.WriteLine(IsVowel('x'));    Console.WriteLine(IsVowel('O'));    Console.WriteLine(IsVowel('P'));}}
Output:
True
False
True
False
True
False
Some patterns support declaring a variable after the pattern. 
Type Pattern: Variable declarations in type patterns are a convenient way to combine both a type check and a cast in one step.
Consider the following:
object o = 42;
if (o is int)
{
    int i = (int) o;
    //...
}
This can be reduced down to a single step using a variable declaration:
object o = 42;
if (o is int i)
{
    //...
}
Positional and Property Patterns: Positional and property patterns also allow a variable declaration after the pattern:
if (DateTime.Now is { Month: 12 } now)
{
    // Do something with now
}
var p = (10, 20);
if (p is (10, 20) coords)
{
   // Do something with coords
}
Here, p and coords contain the same value and coords may little be of any use. But the above syntax is legal and sometimes may be useful with an object that defines a Deconstruct() method. 
Note: Variable declarations are not allowed when using the or and not pattern combinators but are allowed with and.
Sometimes, the value assigned to variables during pattern matching may not be useful. Variable discards allow ignoring the values of such variables. A discard is represented using an underscore(_).
In type patterns: When using type patterns with variable declarations like in the following example:
switch (expression)
{
    case int i:
        Console.WriteLine("i is an integer");
        break;
    ...
}
the variable i is never used. So, it can be discarded:
case int _: 
Beginning with C# 9, it is possible to use type pattern without a variable, which allows getting rid of the underscore as well:
case int:
In positional patterns: When using positional patterns, a discard can be used as a wildcard character to match all values in certain positions. For example, in the Point example above, if we want to match when the x-coordinate is 0 and the y-coordinate does not matter, meaning the pattern has to be matched no matter what the y-coordinate is, the following can be done:
point is (0, _) // will match for all values of y
It is possible to use a type pattern, positional pattern, and property pattern together without combinators.
Syntax:
type-pattern positional-pattern property-pattern variable-name
Consider the Point struct:
struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
    public string Name { get; set; }
    public void Deconstruct(out int x, out int y)
    {
        x = X;
        y = Y;    
    }
}
...
object o = Point() { X = 10, Y = 20, Name = "A" };
Instead of:
if (o is Point p and (10, _) and { Y: 20}) {..}
The following can be written:
if (o is Point (10, _) { Y: 20 } p) {..}
One or more of the patterns can be omitted. However, there must be at least one pattern, and the order when using multiple patterns together must be the same as above. For example, the following is illegal:
if (o is (10, _) Point { Y: 20 } p)
A pattern can consist of several sub-patterns. In addition to the ability to combine multiple patterns with pattern combinators, C# also allows a single pattern to consist of several inner patterns.  
Examples:
Consider the Point struct above and the following object point:
Point point = new Point() { X = 10, Y = 20, Name = "B" };
Type Pattern and var Pattern in a Property Pattern
if (point is { X: int x, Y: var y }) { .. }
Type Pattern and var Pattern in a Positional Pattern
if (point is (int x, var y)) { .. }
Relational Pattern in a Positional Pattern and a Property Pattern
switch (point) 
{
    case (< 10, <= 15): 
         .. 
         break;
    case { X: < 10, Y: <= 15 }:
         ..
         break;
    ..
}
For the next example, consider the following objects:
var p1 = new Point { X = 0, Y = 1, Name = "X" };
var p2 = new Point { X = 0, Y = 2, Name = "Y" };
Property Pattern in a Positional Pattern
if ((p1, p2) is ({ X: 0 }, { X: 0 })) { .. }
amallkrishna
Blogathon-2021
Blogathon
C#
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.