Easily, by a landslide, the most common exception I’ve run into in C# is the NullReferenceException. Second would be an InvalidOperationException. But NullReferenceException wins as most occurred exception hands down. This quick post shows how we can start avoiding the NullReferenceException.
I despise null. I despise null checks.
What if there was a way to get rid of null checks? Luckily there is.
an option type or maybe type is a polymorphic type that represents encapsulation of an optional value; e.g., it is used as the return type of functions which may or may not return a meaningful value when they are applied.
Optional Library
For this example, I’m going to use the Optional package. There are many similar type libraries available on NuGet, but this one is fairly feature rich and serves the purpose for my example.
NullReferenceException
So I’m going to walk through a trivial example of a ASP.NET Core application that is going to return some customer data. The sample code below will throw a NullReferenceException when the incoming customerId is not found in our database.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Obvious answer is to wrap the return statement in an if statement to verify the customer returned from the _customerRepository is not null. If it is, then return a 404.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Our CustomerRepository, could’ve thrown a InvalidOperationException or another type of exception if the customer didn’t exist. But what if instead it returned an Option<Customer> and never returned null.
In the example below, our _customerRepository.GetOptionById(int) now returns an Option<Customer>. This now forces us to specify how to handle if there is a value, and if it is null.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Hopefully this a a simple example of the Option type and how it can be a gateway to removing null and NullReferenceException from your code. Take a deeper look at the docs for the Optional library as there are many other great examples.
Are you using an implementation of Option/Maybe? Let me know in the comments or on Twitter.
I prefer to raise an exception like RecordNotFoundException from GetById. If I call several times GetById, I can wrap all these calls with a try/catch and handle the exception one time.
And I generally don’t return null from a method. If a method can return null, I prefix it with a Try (TryGetById).
My example was pretty contrived. I wouldn’t return null, but it was just to provide an example. Another scenario would of been if the Customer would of had a property that could of been null. Ultimately, the purpose was to demonstrate that an option type eliminates the null, null checks, exceptions and exception handling.
It’s really wrapping the null check in an abstraction rather than eliminating it. There’s no doubt that abstractions like this can be helpful, but they can also make code harder to read. Your last example, I’d suggest, is harder to read than the one before with the explicit null check.
I prefer to raise an exception like RecordNotFoundException from GetById. If I call several times GetById, I can wrap all these calls with a try/catch and handle the exception one time.
And I generally don’t return null from a method. If a method can return null, I prefix it with a Try (TryGetById).
My example was pretty contrived. I wouldn’t return null, but it was just to provide an example. Another scenario would of been if the Customer would of had a property that could of been null. Ultimately, the purpose was to demonstrate that an option type eliminates the null, null checks, exceptions and exception handling.
> would of
> could of
🙁
It’s really wrapping the null check in an abstraction rather than eliminating it. There’s no doubt that abstractions like this can be helpful, but they can also make code harder to read. Your last example, I’d suggest, is harder to read than the one before with the explicit null check.
Exceptions are for exceptional situations.
Missing record is a normal situation (usually) and should not throw an exception.
I code the “happy path”. Everything else is exceptional.