Introduction

When Pattern Matching got added to the C# language like most new syntatic sugar I was initially skeptical about it and the need for it. However like most new language features I started seeing it more, started using it more and it’s become a tool in my tool box.

However unlike some language features it’s not a straight away “go to” feature. Why is this? Because a lot of examples showing how you can use it don’t add much value or aid with readability imho.

Now don’t get me wrong putting together samples to demonstrate concepts is hard but that shouldn’t stop trying to show what you are promoting.

The below code starting code originated from a LinkedIn post a while back but unfortunatly I am unable to locate the original post. If you are aware of it, the original author and/or have the link then please let me know.

Starting Point

The sample started with the below code.

if (product != null && 
    product.Category == "Books" && 
    product.Price > 100)
{
    
}

Now let me start off by saying it’s not inheritantly wrong and the rest of this post is my opinion when it comes to writing readable and maintainable code. Personally I would move the conditional operators to the start of the same line to make it clearer which boolean operation is being executed but other than that this code is ok.

if (product != null 
    && product.Category == "Books"
    && product.Price > 100)
{

}

Convert to Pattern Matching

This can easily be converted to using pattern matching.

if (product is { Category: "Books", Price: > 100 })
{

}

However, the issue I have with pure pattern matching, as above, you have to understand all of the patterns. You have to know that the above code checks that product is an object first and then checks the conditions on the properties. The more complex it gets the less obvious what the code is trying to express.

What About Instance Methods?

One way of making code more readable is adding more descriptive constructs to the code. We could extend the Product class in this instance and add some instance methods on the class definition to aid with readability. These methods can encapsulate the logic but it doesn’t require the developer to need to read the pattern matching logic.

if (product is not null
    && product.IsPriceGreaterThan(100)
    && product.IsCategory("Books"))
{

}

In fact when you’ve got something internal you don’t need to do any pattern matching at all.

public bool IsPriceGreaterThan(int targetPrice) => Price > targetPrice;

This doesn’t get away from the null checking though. So how can we do that?

Extension Methods To The Rescue

Now before I go down this route I know there are developers out there which have a love/hate relationship with extension methods. Like most things there is a time and place to use extension methods. I love extension methods but you have to appreciate how/when to use them.

One of the mistakes I’ve seen in the past is when a developer writes extension methods to encapsulate logic which is only used once. I don’t believe this is a good scenario for extension methods although every situation is different and it depends. If you think about it with regards to what they actually compile out to, which is a static method, then you hopefully you will come out the otherside without too many war wounds.

Some developers might ask “but why are you writing extension methods on a type you have control over?” and this is a great question. I would reply with “would you not write static methods on types you have control over?”. Just because the type is in your control doesn’t mean you shouldn’t write extension methods. At the end of the day they are there to aid with readability and maintainability.

So if you implement extension methods with the similar signature as the instance methods you can update the logic for your condition.

if (product.IsPriceGreaterThan(100)
    && product.IsCategory("Books"))
{

}

This allows increased domain logic and descriptions about what the logic is trying to achieve. You also don’t need to know what is going on under the hood of the methods to use them. Clarity is the key to naming.

public static class ProductExtensions
{
    public static bool IsPriceGreaterThan(this Product? product, int targetPrice) 
        => product is not null && product.Price > targetPrice;

    public static bool IsCategory(this Product? product, string category) 
        => product is not null && product.Category == category;
}

The benefit of these methods are they are pure methods. They wrap the null check logic so you don’t have to manually check each time you want to use these methods. They can be easily unit tested to check the functionality doesn’t get changed without proper intent. The internals are abstracted away and the use cases of them should be something which can be used in different scenarios.

But you might be thinking “but the signature of the extension methods takes a nullable product?”. Yes it does. This allows you to call the extension method from both a nullable and none nullable defined variable/parameter etc.

Be Careful With Extension Methods

The issue with extension methods is it can lead to lack of discoverability. If they get put in an area of your code base which others do not know about they can be lost. This can lead to other developers implementing the same functionality and duplicating complexity to the code base. This can occur when extension methods are put into an Extensions folder for grouping and the namespace matches the folder structure. If the extension methods are defined in the same namespace as the type they are extending this allows for increased discoverability and useability.

There is a caveat on this though. If you are extending a base type (and you probably shouldn’t!) do not put your code in the System namespace (or similar) as it will polute the intellisense across the entire codebase. This will actually reduce the discoverability and make readability worse!

There are scenarios when putting your extension methods in a namespace you do not have control over can aid with discoverability. For example, you are writing a library which integrates with Microsoft.Extensions.DependencyInjection and you want to make it easy for consumers to use your library then put your registration extension methods in that namespace.

Conlusion

Pattern matching, like most concepts in development, has it’s pros and cons. I don’t have an issue with the concepts but I do ask you use the right tool for the right job. Similarly use extension methods in the same way. Both constucts are great tools but they have to be used well.

Remember you don’t just have a hammer and not everything is a nail.