How pattern-matching in C# is lowered
Published by marco on
A while back, I wrote Stop trying so hard to use pattern-matching. I stand by everything i wrote there. I was recently mentoring a very clever programmer who’s new to C# but has cut his teeth on Rust. We were discussing
switch
statements vs. switch
expressions. Which pattern-matching features can you use where? Which features can you combine?
The article Tutorial: Use pattern matching to build type-driven and data-driven algorithms (Learn Microsoft) offers a good introduction. Pattern-matching on objects is lovely (and its been available since C# 7.0[1] (2017)). The version they were using still used “switch statements”. There’s another level called “switch expressions” (available since C# 9 (2020)) that they could have used if they were returning a value. The article C# 9.0: Pattern Matching in Switch Expressions by Thomas Claudius Huber provides the following example,
string favoriteTask = obj switch
{
Developer dev when dev.YearOfBirth == 1980 => $"{dev.FirstName} listens to metal",
Developer dev => $"{dev.FirstName} writes code",
Manager _ => "Create meetings",
_ => "Do what objects do",
};
Speaking of syntactic sugar, you can check out what the compiler would generate using the SharpLab.IO. Throw in any compiling code on the left, and you get the “lowered” version on the right.
If you throw in the example from above with a bit of extra code to make it compile,
using System;
public class C
{
public void M(object obj)
{
string favoriteTask = obj switch
{
Developer { YearOfBirth: >= 1980 and <= 1989 and not 1984 } dev
=> $"{dev.FirstName} listens to heavy metal while coding",
Developer dev => $"{dev.FirstName} writes code",
Manager _ => "Create meetings",
_ => "Do what objects do",
};
}
private class Developer
{
public int YearOfBirth { get; }
public string FirstName { get; } = string.Empty;
}
private class Manager { }
}
You can see that the generated logic is quite straightforward. The snippet below elides the generated code for the Developer
and Manager
classes. It’s not how I would have written it manually, but I bet it’s pretty efficient.
[NullableContext(1)]
public void M(object obj)
{
Developer developer = obj as Developer;
string text;
if (developer == null)
{
text = ((!(obj is Manager)) ? "Do what objects do" : "Create meetings");
}
else
{
int yearOfBirth = developer.YearOfBirth;
if (yearOfBirth >= 1980 && yearOfBirth <= 1989 && yearOfBirth != 1984)
{
Developer developer2 = developer;
text = string.Concat(developer2.FirstName, " listens to heavy metal while coding");
}
else
{
text = string.Concat(developer.FirstName, " writes code");
}
}
string text2 = text;
}