C# 8 came with many new features that make distinct logic branches more apparent for both the developers writing and reading the codebase. Those features include switch expression, property patterns, and tuple patterns.
It can be easy to ignore new features because, well, C# has always worked, right? It is valuable to take a minute to look at these features as they can have lasting benefits to most developers.
In this post, we’ll see how we can combine these features when refactoring code written in previous C# versions.
Classic Approach
Let’s first address the issue that we can write a solution a **1,000* ways, and all be correct. The code we’re about to look at is one of the many ways a developer could write it.
Given we have a list of Visitor
instances, we want to greet those visitors with a custom message.
var visitors = new List<Visitor> {
new Visitor("田中", "Japan"),
new Visitor("Kirill", "Russia"),
new Visitor("Zod", "Krypton"),
new Visitor("Khalid", "USA"),
};
Our final greetings will look like this.
こんにちは田中さん!
Привет Kirill! водка?
Who dis? Zod
GTFO!
Using if
, else
, and switch
we can get the desired output.
private static string HelloClassic(Visitor visitor)
{
string greeting = null;
if (visitor.Name == "Khalid")
{
greeting = "GTFO!";
}
else
{
switch (visitor.Country)
{
case "USA":
greeting = $"Hello {visitor.Name}!";
break;
case "Russia":
greeting = $"Привет {visitor.Name}! водка?";
break;
case "Japan":
greeting = $"こんにちは{visitor.Name}さん!";
break;
default:
greeting = $"Who dis? {visitor.Name}";
break;
}
}
return greeting;
}
As we can see, it’s not a compact solution, spanning 28 lines of code. With C# 8, we can do better!
New Solution
The first step will be to set some groundwork in our Visitor
class. We’ll be adding a Deconstruct
method to take advantage of Tuple Patterns.
internal class Visitor
{
public Visitor(string name, string country)
{
Name = name;
Country = country;
}
public string Name { get; set; }
public string Country { get; set; }
public void Deconstruct(out string country, out string name) =>
(country, name) = (Country, Name);
}
Looking at the Deconstruct
method, we see another feature of C# 8, Positional Patterns. This assignment pattern allows us to set out
variables succinctly. In our case, we want to set the variables of country
and name
to the values of their respective properties.
Our next step is to write out a new method. We’ll use Switch Expression, Property Patterns, and Tuple Patterns.
public static string Hello(Visitor visitor) =>
visitor switch
{
var (country, name) when name == "Khalid" => "GTFO!",
{ Country: "USA" } => $"Hello {visitor.Name}!",
{ Country: "Russia" } => $"Привет {visitor.Name}! водка?",
{ Country : "Japan" } => $"こんにちは{visitor.Name}さん!",
{ Country: _ } => $"Who dis? {visitor.Name}"
};
Let’s break down what’s happening in these eight lines of code.
The first thing we’ll notice is we’re using an expression method, which is made evident by the use of the =>
operator. On the next line, we’ll notice that we are passing the visitor
parameter into our switch
statement, utilizing the switch expression feature of C# 8. Here’s where it gets interesting, remember when we added the Deconstruct
method? The first line of our switch
statement uses tuple patterns and the when
keyword to create the same logic found in the “classic” approach. We then follow up the tuple pattern with the property pattern, where we can access the Country
property of our Visitor
class. Note the use of {
and }
, which tells us we are entering the scope of the instance. On the final line, we use the discard variable of _
to provide a default use case.
static class Program
{
public static void Main()
{
var visitors = new List<Visitor> {
new Visitor("田中", "Japan"),
new Visitor("Kirill", "Russia"),
new Visitor("Zod", "Krypton"),
new Visitor("Khalid", "USA"),
};
foreach (var visitor in visitors)
{
var greeting = Hello(visitor);
WriteLine(greeting);
}
}
public static string Hello(Visitor visitor) =>
visitor switch
{
var (country, name) when name == "Khalid" => "GTFO!",
{ Country: "USA" } => $"Hello {visitor.Name}!",
{ Country: "Russia" } => $"Привет {visitor.Name}! водка?",
{ Country : "Japan" } => $"こんにちは{visitor.Name}さん!",
{ Country: _ } => $"Who dis? {visitor.Name}"
};
}
Running our new method, we get the results we expected.
こんにちは田中さん!
Привет Kirill! водка?
Who dis? Zod
GTFO!
Conclusion
When working through an existing codebase, it can be easy to ignore new features of C# as syntactic sugar or yet another feature syndrom. In the case of switch expressions and its sister features, they can dramatically impact code readability. To take advantage of tuple patterns, we first need a Deconstruct
method, but as you’ve seen in the example above, they are trivial to write. The property pattern makes accessing properties on instances friendly, with a light syntactical approach. Finally, applying the when
keyword rounds out all these patterns to make it possible to target specific logic scenarios.
After reading this post, I hope you give these features a try and let me know what you think in the comments below.