Pattern Matching in C# Explained In Five Minutes

Petey
Petey
Jan 10 · 5 min read
Photo by Austin Distel on Unsplash

Pattern matching enables developers to manage control flows with variables and types that aren’t related by an inheritance hierarchy. In plain English, that simple means it allows developers to use what I would call “ON-THE-GO” variables or type-checks within the control flow itself.

I’m going to cover the four main ones and provide a sample code for each.

  • is type
  • switch statements
  • when clauses in case expressions
  • var declarations in case expressions

Is Type

The is type pattern matching allows the use of type in a control flow and the logic is type-driven versus literal value.

Consider the code snippet below.

public class Motorcycle
{
public string getGreeting() => "I'm a Motorcycle";
}
public class Truck
{
public string getGreeting() => "I'm a truck";
}
public class SUV
{
public string getGreeting() => "I'm an SUV";
}
public class BUS
{
//class related logic goes here
public string getGreeting() => "I'm a Bus";
}

With the is type, you can now code against a vehicle type. For instance, if I wanted to have a logic based on each vehicle type, I could do something like the code in the snippet below.

public static string GetVehicleGreeting(object vehicle)
{
if (vehicle is Motorcycle m)
return m.getGreeting();
if (vehicle is Truck t)
return t.getGreeting();
if (vehicle is SUV s)
return s.getGreeting();
if (vehicle is BUS b)
return b.getGreeting();
throw new ArgumentException(
"unsupported vehicle type",
nameof(vehicle));
}

Behind the scene, the is expression tests the variable and assigned it to a new appropriate variable type. The old way of doing this would be.

var m = (Motorcycle)vehicle;
return m.getGreeting();

The is type expression works for both value and reference type. If one of the class were a struct instead of a class, this would still work.


Switch Statements

The switch statement expression behaves similarly to the is type, the only difference is that the control flow is a case versus an if statement. This might not seem like a big deal at first, but before pattern matching was introduced, a switch statement was limited to numerical and string types.

Consider this enum.

public enum PowerLevel
{
weak, //=>0
ok, //=>1
strong //=>2
}

A typical switch before pattern matching using our enum int values would look like this.

public static void PrintInsult(PowerLevel level)
{
switch (level)
{
case PowerLevel.weak:
Console.WriteLine("You need some milk");
break;
case PowerLevel.ok:
Console.WriteLine("You're flirting with danger");
break;
case PowerLevel.strong:
Console.WriteLine("You're untouchable now");
break;
default:
Console.WriteLine("Keep your day job");
break;
}
}

If we wanted to accomplish the same behavior using a switch statement expression for our vehicle classes as we did with the is type, the code would look like this.

public static string GetVehicleGreeting(object vehicle)
{
switch (vehicle)
{
case Motorcycle m:
return m.getGreeting();
case Truck t:
return t.getGreeting();
case SUV s:
return s.getGreeting();
case BUS b:
return b.getGreeting();
default:
throw new ArgumentException(
"unsupported vehicle type",
nameof(vehicle));
}
}

When/Case expressions

Let’s add a new property named Mileage to both the Truck and SUV classes.

public class Truck
{
public double Mileage { get; set; }

//class related logic goes here
public string getGreeting() => "I'm a truck";

}
public class SUV
{
public double Mileage { get; set; }

//class related logic goes here
public string getGreeting() => "I'm an SUV";
}

If we wanted to use this property to check its value and perform some action based on our check, we could do that with a when/case expression. Our check will display some funny message instead of the message from the getGreeting method.

public static string GetVehicleGreeting(object vehicle)
{
switch (vehicle)
{
case Truck t when t.Mileage <= 100:
case SUV s when s.Mileage <= 100:
return "You got yourself a brand new vehicle";
case Truck t when t.Mileage >= 100000:
case SUV s when s.Mileage >= 100000:
return "It's time to upgrade vehicle";
case Truck t:
return t.getGreeting();
case SUV s:
return s.getGreeting();
default:
throw new ArgumentException(
"Unsupported vehicle type",
nameof(vehicle));
}
}

This is especially useful if you’re calling members of a class you’re not allowed to modify and need to apply small logic or in our case perform a field comparison. The old way required me to add my logic inside the case body instead of as part of the case itself.


Var/Case expressions

The var/case behaves similarly as the when/case with one major difference. The when/case, as we’ve already seen earlier, performs one single operation inline, then the case body is run if that condition is met. Var/case performs an additional step before comparing.

Suppose all of our vehicle classes had a property named SalesCode.

Our use case is to return a vehicle description from a method we’ll create that takes in the SalesCode value as a parameter to perform different logic based on each unique SalesCode. However, if that value is null or empty, a specific message needs to be returned. You could do that with an if statement as well, but you’d have to first do a null-coalescing assignment, then do a null or empty check to return the specific message. I’m sure there’s always another way of doing the same thing, but one of the cleanest ways of doing in my option is to use the var/case expression statement.

public static string GetVehicleDescription(string salesCode)
{
switch (salesCode)
{
case "salesALL":
case "SaleEverything":
case "SellWhateverWeHaveLeft":
return "This is a bargain price, IT MUST GO";
case var price when (price?.Trim().Length ?? 0) == 0:
return "UPDATE SalesCode IMMEDIATELY";
default:
return "SalesCode is invalid";
}
}

That’s all on pattern matching for now. There are many cases where using pattern matching can be beneficial and other cases where it’s not the best choice. As always, it is up to the software engineers to use their best judgment in each case. I hope this has been helpful and thank you for making it to the end. Happy coding.

If you like this article, you might like my other related articles

The Startup

Medium's largest active publication, followed by +563K people. Follow to join our community.

Petey

Written by

Petey

Husband, father, engineer, musician, and writer by luck. I write mostly about whatever comes to mind. Follow me on this crazy writing journey if you dare.

The Startup

Medium's largest active publication, followed by +563K people. Follow to join our community.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade