Code Focused
Pattern Matching in C# 7.0 Case Blocks
Welcome to the 21st century, C#, now that case blocks support a variety of pattern-matching formats.
Switch statements and the case blocks they contain have been a mainstay of C-language flow-control syntax since the initial incarnation of the language back in the 1970s. C# inherited the overall switch statement constructs from C, including its mundane constant-based selection process. Except for the requirement that a break statement appear at the end of each case block, little has changed in four decades.
Visual Basic, by contrast, sports supercharged Case blocks that enable complex comparisons that are far more interesting than simple lists of constants:
Select Case age
Case 50
ageBlock = "the big five-oh"
Case 80, 81, 82, 83, 84, 85, 86, 87, 88, 89
ageBlock = "octogenarian"
Case 90 To 99
ageBlock = "nonagenarian"
Case Is >= 100
ageBlock = "centenarian"
Case Else
ageBlock = "just old"
End Select
C# 7.0, as part of Visual Studio 2017, brings switch statements into the 21st century, thanks to the new pattern-matching features added to case blocks. Three distinct matching formats are now possible. (Be aware that all samples listed in this article use the Visual Studio 2017 Release Candidate, and some elements might change before the final release.) The first format is the same tried-and-true constant syntax that has been around since the first C# release:
switch (age)
{
case 50:
ageBlock = "the big five-oh";
break;
case 80:
case 81:
case 82:
// ... and so on ...
case 89:
ageBlock = "octogenarian";
break;
default:
ageBlock = "just old";
break;
}
The new type pattern enables matches based on a specific class or structure. These expressions include a type name followed by a new variable instance that can be accessed within the code of the matching case block. Null instances never match these expressions, and instead must be handled (if desired) using a null-constant matching block:
// ----- Assume that spaceItem is of type SpaceType,
// and that Planet and Star derive from SpaceType.
switch (spaceItem)
{
case Planet p:
if (p.Type != PlanetType.GasGiant)
LandSpacecraft(p);
break;
case Star s:
AvoidHeatSource(s);
break;
case null:
// ----- If spaceItem is null, processing falls here,
// even if it is a Planet or Star null instance.
break;
default:
// ----- Anything else that is not Planet, Star, or null.
break;
}
One implication of this syntax is that switch statements are no longer limited to core data types like integers and strings. You can include variables or expressions of any .NET type within both the switch statement and the case block expressions. Also, null is now a valid constant expression. It catches null reference-type objects, even if those same types are mentioned in a non-null fashion in other blocks.
The third format, known as var patterns, uses the var keyword, and simply copies the source test variable or expression into a new, named variable:
switch (testVariable)
{
case var blockVariable:
// ----- Use blockVariable here as needed.
break;
}
This seems a bit pointless, until you add the when clause, also new to case blocks in C# 7.0. When attached to the end of a case block expression, the when keyword enables access to Visual Basic-style Case block comparisons:
switch (age)
{
case 50:
ageBlock = "the big five-oh";
break;
case var testAge when (new List<int>()
{ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89 }).Contains(testAge):
ageBlock = "octogenarian";
break;
case var testAge when ((testAge >= 90) & (testAge <= 99)):
ageBlock = "nonagenarian";
break;
case var testAge when (testAge >= 100):
ageBlock = "centenarian";
break;
default:
ageBlock = "just old";
break;
}
The when clause also works with type-style patterns:
switch (spaceItem)
{
case Planet p when (p.Type != PlanetType.GasGiant):
LandSpacecraft(p);
break;
case Planet p:
// ----- Gas giants fall here.
break;
Visual Studio 2015 and its associated C# 6.0 release included a similar when-clause pattern as part of the structured error-handling system:
// ----- Assumes a Boolean variable named hasOtherErrors
try
{
// ----- Error-prone code here.
}
catch (Exception ex) when (hasOtherErrors == true)
{
// ----- This isn't the first error.
}
catch (Exception ex)
{
// ----- First-time error.
}
When logic branching requires anything more than a simple value-type comparison, C# developers have routinely reached for if-else statements to guide data processing. With the new case-block enhancements in C# 7.0 and Visual Studio 2017, switch statements have now become a reasonable option for expression-rich flow-control processing.
About the Author
Tim Patrick has spent more than thirty years as a software architect and developer. His two most recent books on .NET development -- Start-to-Finish Visual C# 2015, and Start-to-Finish Visual Basic 2015 -- are available from http://owanipress.com. He blogs regularly at http://wellreadman.com.