This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.
Title
How pattern-matching in C# is lowered
Description
<img attachment="pattern-matching-in-c-sharp.webp" align="right">A while back, I wrote <a href="{app}/view_article.php?id=4657">Stop trying so hard to use pattern-matching</a>. 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 <c>switch</c> <i>statements</i> vs. <c>switch</c> <i>expressions</i>. Which pattern-matching features can you use where? Which features can you combine?
The article <a href="https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/tutorials/pattern-matching" source="Learn Microsoft">Tutorial: Use pattern matching to build type-driven and data-driven algorithms</a> offers a good introduction. Pattern-matching on objects is lovely (and its been available since <a href="https://devblogs.microsoft.com/dotnet/whats-new-in-csharp-7-0/">C# 7.0</a><fn> (2017)). The version they were using still used "switch statements". There's another level called "switch expressions" (available since <a href="https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/">C# 9</a> (2020)) that they could have used if they were returning a value. The article <a href="https://www.thomasclaudiushuber.com/2021/02/25/c-9-0-pattern-matching-in-switch-expressions/" author="Thomas Claudius Huber" source="">C# 9.0: Pattern Matching in Switch Expressions</a> provides the following example,
<code>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",
};</code>
Speaking of syntactic sugar, you can check out what the compiler would generate using the <a href="https://sharplab.io">SharpLab.IO</a>. 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,
<code>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 { }
}</code>
You can see that the generated logic is quite straightforward. The snippet below elides the generated code for the <c>Developer</c> and <c>Manager</c> classes. It's not how I would have written it manually, but I bet it's pretty efficient.
<code>[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;
}</code>
<hr>
<ft>We're on <a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13">C# 13</a> in April 2025, with <a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14">C# 14</a> in the works for November, 2025.</ft>