C# Pattern matching

Overview of scenarios where you can use pattern matching. These techniques can improve the readability and correctness of your code.

Reduce nested if-else

I have a complex nested if-else statements scenario

// DON'T DO THIS
if (order.Status == "Pending")
{
	ProcessPendingOrder(order);
} else if (order.Status == "Completed")
{
	ProcessCompletedOrder(order);
} else if (order.Status == "Cancelled")
{
	ProcessCancelledOrder(order);
} else
{
	throw new InvalidOperationException("Unknown status");
}

instead do this

order.Status switch
{
	"Pending" => ProcessPendingOrder(order),
	"Completed" => ProcessCompletedOrder(order),
	"Cancelled" => ProcessCancelledOrder(order),
	_ => throw new InvalidOperationException("Unknown status")
};

Using it as a method

You can test a variables to find a match on specific values

public State PerformOperation(string command) =>
	command switch
	{
		"SystemTest" => RunDiagnostics(),
		"Start" => StartSystem(),
		"Stop" => StopSystem(),
		_ => throw new ArgumentException("Invalid string value", nameof(command)),
	};

_ is the discard pattern that matches all values. It handles any error conditions where the value doesn’t match one of the defined values.

Another example

We have the following class

IAnimal animal = new Cat("Fred", 12);

then with a single if we can determine if the animal is more than 10 years old and has the name of fred

if(animal is Cat { CatYears: >10, Name: "Fred"} myCat)
{
	Console.WriteLine($"{myCat.CatYears} old");
}

Null checks

One of the most common scenarios.

string? message = MaybeString();
if(message is not null) 
{
	Console.WriteLine(message);
}

Relational patterns

You can use relational patterns to test how a value compares to constants.

string WaterState(int tempInFahrenheit) =>
	tempInFahrenheit switch
	{
		(>32) and (<212) => "liquid",
		<= 32 => "solid",
		>= 212 => "gas"
	};

Multiple inputs

You can write patterns that examine multiple properties of an object.

consider the following Order record

public record Order(int Items, decimal Cost);

this examines the number of items and the value of an order to calculate a discount

public decimal CalculateDiscount(Order order) =>
	order switch
	{
		{ Items: >10, Cost: >1000.00m } => 0.10m,
		{ Items: >5, Cost: > 500.00m } => 0.05m,
		{ Cost: >250.00m } => 0.02m,
		null => throw new ArgumentNullException(nameof(order), "can't calculate on null"),
		var someObject => 0m,
	};

List patterns

Since c# 11 we may check for list emptiness

check for list emptiness

if (items is [])
	throw new ArgumentException("Input must not be empty");

if (items is not [])
	Console.WriteLine("list is not empty");

Reference(s)

https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching
https://ianvink.wordpress.com/2021/12/11/part-1-c-10-pattern-matching-or-the-death-of-if-else/
https://dev.to/ahmedshahjr/improve-your-c-code-with-pattern-matching-5g7c
https://endjin.com/blog/2023/03/dotnet-csharp-11-pattern-matching-lists