DEV Community

Cover image for Eliminating Nulls in C# with Functional Programming
Matt Eland
Matt Eland

Posted on

Eliminating Nulls in C# with Functional Programming

This is a short and sweet article showing how the Option class can bring functional programming concepts to C# codebases and prevent null reference exceptions.

This article will be using the extremely popular language-ext library as the most popular .NET functional programming library built for object oriented languages (aside from F#).

Installing the Language-Ext Library

In order to work with the language-ext library, you will need to add a NuGet reference to the package. This can be done via NuGet Package Manager in Visual Studio or by running Install-Package LanguageExt.Core from the package manager console.

What is an Option?

Options are a reference type that either contain some value or have none. As an example, look at the following method that searches a dictionary and returns a value from it:

public Option<ResumeKeyword> GetKeyword(
   IDictionary<string, ResumeKeyword> keywords, 
   string word) { 

  // Ensure we're searching for a lower-cased non-null word
  string key = word ?? word.ToLowerInvariant() : string.Empty;

  // If the keyword is present, return it
  if (keywords.ContainsKey(key)) {
    return keywords[key]; // Implicitly converted to Some<ResumeKeyword>
  }

  // No keyword found
  return Option<ResumeKeyword>.None;
}
Enter fullscreen mode Exit fullscreen mode

In this method, since the return type is Option<ResumeKeyword>, the return type will either be of Some<ResumeKeyword> or None. The result will never explicitly be null so callers will never have to worry about null values.

The compiler is able to convert a standard value to a Some value, but the syntax of the None return type is less than ideal as we can start to see the added complexity of working with Option.

Thankfully, the return type can be simplified if we add the following using at the top of the file: using static LanguageExt.Option<ResumeKeyword>;

This allows us to simply return None; instead of doing return Option<ResumeKeyword>.None; which reads significantly better.

Working with Options

Now that you have an Option, you want to be able to work with it. This is where the appeal of Option starts to come into play. Because we're working with an Option the compiler doesn't let us make potentially buggy code like the following:

// Give a bump for various words in the title
foreach (var word in job.Title.Split())
{
    var keyword = FindKeyword(keywordBonuses, key);
    jobScore += keyword.Value; // Does not compile
}
Enter fullscreen mode Exit fullscreen mode

This code does not compile because keyword is an Option<ResumeKeyword> instead of a ResumeKeyword and so it cannot access the members of ResumeKeyword directly. This inconvenience is actually a good thing because we know that FindKeyword can sometimes not find any keyword match, which would result in a NullReferenceException in a standard application if we forgot to check for null.

Instead we write the following:

// Give a bump for various words in the title
foreach (var word in job.Title.Split())
{
    var keyword = FindKeyword(keywordBonuses, key);
    jobScore += keyword.Some(k => k.Value)
                       .None(0);
}
Enter fullscreen mode Exit fullscreen mode

Here we're saying that we want to increase the score by the Value of the ResumeKeyword if Some keyword is present and, if None is present, score the word as 0 or filler.

The compiler's type checking will enforce these rules and make sure we arrive at the correct types, forcing us to think about whether or not the value is present and act accordingly.

For scenarios where you only want to do something if a value is present or absent and you don't need the actual value, you can check the IsSome or IsNone properties. For example:

if (keyword.IsNone) {
   Console.WriteLine($"No value defined for keyword '{word}');
}
Enter fullscreen mode Exit fullscreen mode

What about nullable types?

.NET has had the concept of nullable types for awhile via Nullable<T> (often coded as T? myVar).

A nullable type is a way of representing a value type as a nullable object. This is different from an Option because:

  1. Options work with both reference and value types
  2. While Nullable<T> offers HasValue and Value members, Option<T> offers convenience methods to react in specific ways to Some or None conditions as we saw above.

Summary

Language-Ext's Option class forces you to make intelligent decisions and work with potentially missing values in a safe way.

The downside of this is that it adds complexity to both reading and writing your code, but in quality critical aspects of your codebase or areas where null values are frequently possible, it might make sense to incorporate Option<T> into your code.

Also, bear in mind that Option<T> is only a slice of the capabilities offered by Language-Ext. Check out the full library to read more or follow me for notifications of future posts.

If you're curious about more ways of eliminating entire classes of defect, check out my article on making defects impossible.

Oldest comments (13)

Collapse
 
peledzohar profile image
Zohar Peled

It might be just me, but I like nulls
I mean, I like using null as a way to say "the reference does not have a contet" - meaning the method didn't return a value. That's basically the same as None on your example.

Collapse
 
integerman profile image
Matt Eland

It's not just you. You either have something or you don't, so it's a simple concept. The problem with nulls is that you don't have to think about null cases when implementing routines and so if you don't test your application in such a way that a null path is followed, you might have NullReferenceException waiting behind a number of rocks. This is essentially a way of enforcing that you are explicitly handling nulls, but it does come at a price.

It's not going to be for everyone, but it is an Option (pun intended).

Collapse
 
peledzohar profile image
Zohar Peled

Yeah, I've read and seen the you-tube where Tony Hoare talks about his Billion Dollar Mistake - and I absolutely agree that a NullReferenceException has no place in production code - That's why c#8 came up with nullable reference types, making standard reference types non-nullable - but I suspect that leaves you with no option of indicating a non-existsing value, and that's when the Option class can really come in handy. Anyways, it's always good to pick up coll new tools, so thanks for the article! :-)

Thread Thread
 
seangwright profile image
Sean G. Wright

Reference types can still be null in C#8... they just behave more like value types.

They have to explicitly opt into null.

This is the big problem with null and reference types currently... null is not the same as the type itself, it is a separate type.

All reference types in C# are type unions of the type and null but the language doesn't flow this type unions through our code so it's very easy to overlook.

Thread Thread
 
peledzohar profile image
Zohar Peled

Yes, that's why the concept is called nullable reference types (though I think it should have been called non-nullable reference types, since that's the big change...)

I think the problem with nulls is that many developers, even well experienced, sometimes forget the simple fact that any reference type might be null, especially if it's exposed to changes outside your class (like a get;set; property for example). In fact, as the years go by I find myself more and more in agreement with SO Legend Jon Skeet - as often as you can, make your types immutable. Of course, that doesn't prevent the need for null validations, but at least inside your own types you can be safe from nulls (and other unexpected behaviors).

Thread Thread
 
seangwright profile image
Sean G. Wright

Ya, strict constructors, guard clauses, and immutable state (or at least protected invariants) can go a long ways towards evicting null from your codebase (except where it's appropriate, of course).

If we take an Onion architecture approach for our application, and guard against nulls on the outside of our domain, then we can be safe from it within our business logic, which is where we typically have issues with null.

I like that there are many different ways to protect against or at least carefully handle the case of reference types with null values.

Thread Thread
 
integerman profile image
Matt Eland

That's typically what I gravitate towards - validating on public methods in public classes - then farming things out to private methods or internal classes.

Collapse
 
akashkava profile image
Akash Kava

Checking for null is simple step, always marshaling Option is exactly same with few additional lines of code. I never had problem with null, because exception can halt further processing.

In this case, entire chain needs to have special line case of None, and another problem is digging root cause of what failed could be nightmare as you have no idea whether method returned a default or something went wrong.

Thread Thread
 
integerman profile image
Matt Eland

I really love the conversations this thread sparked. I'm really enjoying seeing the perspectives the community has to bring on quality in general.

Collapse
 
seangwright profile image
Sean G. Wright

I'm a big fan of this pattern for removing incorrect nulls from an app.

I was first introduced to it in this blog enterprisecraftsmanship.com/posts/... and my explorations into F#.

I did look at the Ext library awhile ago for ideas but didn't want all my code to start taking dependencies on such foundational pieces from a 3rd party package.

I always find it tricky to know when my C# is getting too functional for it's own good... 😁

Collapse
 
integerman profile image
Matt Eland

I view .NET as basically a plundering band of nomads at the moment - taking the best concepts and even syntax from other languages. It's what keeps the platform relevant, but it also makes .NET a very broad subject to learn.

The Option pattern isn't too hard to code on your own if you don't want external dependencies, though some of the syntactic sugar from automatic conversions of Some values might not be as polished.

Honestly, a lot of the value in things like this is just the concept and the level of polish and documentation on something. I'm a huge fan of the Scientist library, for example, and that library is actually really simple under the covers, but represents a novel approach.

Collapse
 
anras573 profile image
Anders Bo Rasmussen

I'm using the Maybe Monad, which appears to be almost the same as Options.

I also like the word 'maybe' better than 'option', so I might be biased 😄

Imo Maybe.IsSome and Maybe.IsNone sounds better than Option.IsSome and Option.IsNone

Collapse
 
seangwright profile image
Sean G. Wright

I use a Result<T> type, mostly as the return for calls to services / data access abstractions.

I then have conditional blocks like if (result.IsFailure) { ... } or if (result.IsSuccess) { ... }.

If it's a success, then the .Value type T property will be populated. If it's a failure then the .Error string property will be populated.

I think Option, Maybe, Result are all valid and the word used depends on your team. Option/Maybe feel a bit more functional and academic. I like them in F# but they feel a bit foreign in C#.