top of page

C#: Dealing with NULL values in a safe and elegant way.


One of the recurrent problems that I see in almost all C# codebases is the poor way in which developers implicitly or explicitly deal with NULL values. When you create a method that can potentially, implicitly or explicitly, return a NULL value, you set yourself up for a lot of trouble. Some of it comes in the form of dozens or hundreds of NULL checks, which creates a lot of boilerplate code and a maintenance problem. Although there is a worse scenario, forgetting about checking for NULL will make you fall prey to the infamous NULL Reference Exception.


Let’s think about the following piece of code:

public Product GetByName(string productName)
=> Products.FirstOrDefault(product => product.Name == productName);

You have seen this type of code before. What happens if there is no product with the given name? You are right, the value NULL is returned. There are very obvious problems with this and one that is not that obvious, but equally bad. Now, all the consumers of this method need to guard themselves against a possible NULL Reference Exception, and forgetting to do so could have devastating consequences for you you, your code, and your customer. The not so obvious problem has to do with the fact that when looking at a method’s signature, there is no way to tell if it can possibly return NULL or not, so developers need to go to the implementation and analyze the code to try to deduce if this is a possible scenario.


The problem becomes massively larger when you look at a real codebase where you have long chains of method calls, and Method1 calls Method2 and this one, in turn, calls Method3 and Method4 and…you get the point. In summary, there is nothing warning us about this potentially catastrophic issue, we need to manually investigate through many lines of code if we want to know if it could be a problem (or simply guard all the time no matter what), we are not forced to deal with it (it’s up to the developer to explicitly check and deal with NULLs).


We all know what happens when developers are free to decide if we are going to do something or not, and that is the reason why a great deal of the time, strongly-typed functional programming languages force developers to deal with this type of things, instead of leaving it up to their mood, memory or nature.

How many times have you seen code similar to this:

var product = productRepository.GetByName("Galaxy Watch");if (product != null)
{
   // ...
}
else
{
   // ...
}

Have you ever thought that there has to be a way to remove boilerplate code like this and stop it from cluttering our codebase with repetitive logic that is just meant to do the same thing over and over again? What if there was a way to do it while also clearly communicating that there is a possibility of a value not being present or found, and at the same time, forced developers to deal with that scenario, not leaving anything up to you to decide or remember?


The OOP community is familiar with something called The NULL Object Pattern. After using this pattern for over a decade, it was clear to me that it is not flexible enough, and that it creates a lot of code (since you typically create a NULL Object implementation for each class where you want to deal with a potential NULL value). I also noticed that it leads to developers still using if/else statements to see if a given instance is a NULL Object instance or a real one. Long story short, The NULL Object Pattern did not cut it for me.


Sure enough, the Functional Programming community had a solution to this already, all we OOP developers had to do was learn how it worked and use it. F# has a type called Option (https://fsharpforfunandprofit.com/posts/the-option-type/), that allows you to deal with this problem. C# developers do not have a type like this out of the box, but there are many implementations that you can find online, most of them go by the name of Maybe (instead of Option). Let’s see next how we can use this approach.


If you want to follow along, go ahead and install the LeanSharp Nuget Package (you can find the Github repository at https://github.com/ericrey85/LeanSharp/)

Install-Package LeanSharp

Go ahead and add this using statement at the top of the .cs file where you will use the Maybe class:

using LeanSharp.Extensions;

Let’s now refactor our previous example to use Maybe (known as the Maybe Monad).

public Maybe<Product> GetByName(string productName)
=> Products
     .FirstOrDefault(product => product.Name == productName)
     .ToMaybe();

The first notable thing is that by looking at the signature of this method (or the Interface it implements), it is very clear that there could be a scenario where the Product is not found (no need to investigate to deduce this). Not only it communicates this to a developer but it also forces her/him to deal with this fact. Let’s see now a possible way to use this method.

var productPrice = product.GetOrElse(p => p.Price, -1);

This Maybe class contains a number of methods (GetOrElse being one of them) that safely allow you to declaratively express what you want to do in case of a value being present, or being missing. In the code above, the variable productPrice will end up with the real product price if the Product was found, or the value -1 if it was not (do not spend too much time thinking whether we want to do this or not from a Business Logic perspective, that is not the point).


As you can see, there is logic somewhere in the Maybe class that is protecting you from NULL values, you do not have to repeat yourself (DRY principle) in a thousand different places to do this. You will also not forget about it, since the method’s signature forces you to deal with the Maybe type.


Now, let’s see an example when dealing with collections. I keep seeing a lot of developers that when in need to check if a given collection (array, list, etc) contains any elements, repeat themselves with the same logic:

var products = productRepository.GetByCategory("Electronics");if (product != null && products.Any())
{
   // ...
}

I see this repetitive logic time and time again. C# developers that are more up-to-date with its more “modern” features, will usually simplify this conditional logic to be:

if (products?.Any() == true)

In my opinion, this is better, but you will still have to remember to do this any time you need this type of logic, and you will also have team members that will not be used to using this syntax and will use the first one (which will produce inconsistency). By using LeanSharp, you can safely check if a collection has elements or not and not even worry or spend time trying to figure out if checking for NULL is even needed in the first place. Take a look at the below example:

if (products.SafeAny())

If you need to use a predicate, you can then do something like:

if (products.SafeAny(p => p.Price > 100))

The intention of LeanSharp is to help create a common and consistent way of programming in C#, which also allows us to boost developer productivity (by creating code that is easier to maintain) and greatly reduce your bug count (by addressing known bad coding practices and enforcing good ones).


If you noticed, the Maybe monad was used in a scenario where it was perfectly valid to not find a Product. What if you have a scenario where not finding a value or not having a value present would signify an “unexpected” problem? You usually see those scenarios being coded like this:

// Assume the variable newCount exists in this context.
if (product == null)
{
  throw new InvalidOperationException("The product was not found.");
}
else
{
   product.UpdateInventory(newCount);
}

Most of the time, I like to handle this in a different way, using something known as Railway-Oriented Programming (ROP) -https://fsharpforfunandprofit.com/rop/. This topic deserves its own article (more like its own book to be honest). This idea was also taken from Functional Programming, where there is something called The Either Monad (because you can have either a Left — error, or a Right — actual result).


LeanSharp contains an implementation of ROP for C#, it’s a generic Result<TSuccess, TFailure> class that contains Extension Methods that allow you to compose code, and at the same time protect you from executing logic that should not be executed in case of failure. The above case could be refactored to look like:

// Assume the variable newCount exists in this context.
product  
  .IfNull("The product was not found.")
  .Map(p => p.UpdateInventory(newCount));

In the above code, the UpdateInventory method will not be called if the product instance was NULL. The IfNull method “lifts” the product instance into a Result<Product, String> instance, where then you can use a Fluent API-like style that allows you to chain method calls (in a safe manner), which is nothing more than a way of function/method composition.


My intention here is not to tell you that the ideas you see in this article represent the best way of doing things, or that they are better than what you are currently doing. I just want to share with you some of the things I do as a C# developer that most of the time have been a better alternative to the traditional way of doing things in C#. After a couple of decades writing lines of code and making a lot of mistakes, I believe some of these ideas can solve some of the problems that you may have in your current C# codebase.



Source: Medium


The Tech Platform

0 comments

Commenti


bottom of page