Pattern matching concept was first introduced in C# 7.0, and it checks if an object reflects a certain shape. It will also check that it can extract information from the value when it has the matching shape.
if or switch statements that test values, when those statements match, you extract and use information from that value.is and switch.is Type PatternBefore C# 7.0, you would need to test each type in a series of if and is statements. Let's consider the following simple example, which a classic expression of the type pattern.
if (person is Teacher)
{
Console.WriteLine("Salary: {0}", ((Teacher)person).Salary);
}
else if (person is Student)
{
Console.WriteLine("GPA: {0}", ((Student)person).GPA);
}
As you can see, it is testing a variable to determine its type and taking a different action based on that type.
In C# 7.0, this code becomes simpler using extensions to the is expression to assign a variable if the test succeeds.
if (person is Teacher t)
{
Console.WriteLine("Salary: {0}", t.Salary);
}
else if (person is Student s)
{
Console.WriteLine("GPA: {0}", s.GPA);
}
The is expression tests both the variable and assigns it to a new variable of the proper type in C# 7.0 and later.
is expression works with value types as well as reference types.t and s are only in scope and definitely assigned when the respective pattern match expressions have true results.switch Type PatternThe traditional switch statement was a pattern expression, and it supported the constant pattern by comparing a variable to any constant used in a case statement.
int caseSwitch = 1;
switch (caseSwitch)
{
case 1:
Console.WriteLine("Case 1");
break;
case 2:
Console.WriteLine("Case 2");
break;
default:
Console.WriteLine("Default case");
break;
}
The only pattern supported was the constant pattern, and it was also limited to numeric types and the string type.
In C# 7.0, those restrictions have been removed, and you can now write a switch statement using the type pattern as shown below.
switch (person)
{
case Teacher t:
return "Salary: " + t.Salary;
case Student s:
return "GPA: " + s.GPA;
default:
throw new ArgumentException(message: "It is not a recognized person", paramName: nameof(person));
}
Each case is evaluated, and the code beneath the condition that matches the input variable is executed. The case statement statement requires that each case end with a break, return, or goto.
when ClauseYou can also use a when clause on the case label to specify an additional condition. Let's consider the following example in which we specified a condition using a when clause on the case label.
switch (person)
{
case Teacher t when t.Salary == 0.0:
case Student s when s.GPA == 0.0:
return "";
case Teacher t:
return "Salary: " + t.Salary;
case Student s:
return "GPA: " + s.GPA;
default:
throw new ArgumentException(message: "It is not a recognized person", paramName: nameof(person));
}
You can also add a null case to ensure the argument is not null as shown below.
switch (person)
{
case Teacher t when t.Salary == 0.0:
case Student s when s.GPA == 0.0:
return "";
case Teacher t:
return "Salary: " + t.Salary;
case Student s:
return "GPA: " + s.GPA;
case null:
throw new ArgumentNullException(paramName: nameof(person), message: "Person must not be null");
default:
throw new ArgumentException(message: "It is not a recognized person", paramName: nameof(person));
}
The special behavior for the null pattern is interesting because the constant null in the pattern doesn't have a type but can be converted to any reference type or nullable value type.