NullReferenceException & LINQ XOrDefault methods

canro91

Cesar Aguirre

Posted on August 14, 2023

NullReferenceException & LINQ XOrDefault methods

I originally posted an extended version of this post on my blog a couple of months ago. It's part of a series called "Goodbye, NullReferenceException."

Last time, we covered when NullReferenceException is thrown. This time, let's learn how to prevent NullReferenceException when working with LINQ XOrDefault methods.

If we don't check the result of LINQ FirstOrDefault(), LastOrDefault(), and SingleOrDefault() methods, we could get NullReferenceException.

In general, the XOrDefault() methods return null when the source collection has reference types, and there are no matching elements.

Let's focus on FirstOrDefault(). It finds the first element of a collection or the first element matching a condition. If the collection is empty or doesn't have matching elements, it returns the default value of the collection's type. For reference types, that's a null.

For example, let’s find in our catalog of movies a “perfect” film,

var movies = new List<Movie>
{
    new Movie("Shrek", 2001, 3.95f),
    new Movie("Inside Out", 2015, 4.1f),
    new Movie("Ratatouille", 2007, 4f),
    new Movie("Toy Story", 1995, 4.1f),
    new Movie("Cloudy with a Chance of Meatballs", 2009, 3.75f)
};

// We don't have any perfect movie on our list,
// FirstOrDefault returns null
var theBest = movies.FirstOrDefault(movie => movie.Rating == 5.0);
Console.WriteLine(theBest.Name);
//                ^^^^^
// Boooom!

record Movie(string Name, int ReleaseYear, float Rating);
Enter fullscreen mode Exit fullscreen mode

We could simply check if theBest is null before using it. But, there are other alternatives to prevent the NullReferenceException when working with XOrDefault methods.

1. XOrDefault overloads from .NET 6.0

.NET 6.0 released some new LINQ methods and overloads. With .NET 6.0, we can pass a second parameter to the XOrDefault() methods as a default value if the collection is empty or there are no matching elements. Like this,

var movies = new List<Movie>
{
    new Movie("Shrek", 2001, 3.95f),
    new Movie("Inside Out", 2015, 4.1f),
    new Movie("Ratatouille", 2007, 4f),
    new Movie("Toy Story", 1995, 4.1f),
    new Movie("Cloudy with a Chance of Meatballs", 2009, 3.75f)
};

var theBestOfAll = new Movie("My Neighbor Totoro", 1988, 5);

// With .NET 6.0 FirstOrDefault()
var theBest = movies.FirstOrDefault(
                    movie => movie.Rating == 5.0,
                    theBestOfAll);
                    // ^^^^^
Console.WriteLine(theBest.Name);  // "My Neighbor Totoro"
//                ^^^^^
// We're safe here in any case

record Movie(string Name, int ReleaseYear, float Rating);
Enter fullscreen mode Exit fullscreen mode

Since we don't have any perfect movie in our catalog, FirstOrDefault() should return null. But, we're passing theBestOfAll as a custom default. We're safe to use the result of FirstOrDefault() without worrying about null and NullReferenceException.

Kitty on a box

That's an Option<Cat>...Photo by Jiawei Zhao on Unsplash

2. Use Optional's XOrNone

Functional languages like F# or Haskell use a different approach for null. Instead of null, they use an Option or Maybe type.

With the Option type, we have a “box” that might have a value. It’s the same concept of nullable ints.

For example,

int? maybeAnInt = null;

var hasValue = maybeAnInt.HasValue;
// false

var dangerousInt = maybeAnInt.Value;
//                            ^^^^^
// Nullable object must have a value.

var safeInt = maybeAnInt.GetValueOrDefault();
// 0
Enter fullscreen mode Exit fullscreen mode

With nullable ints, we have a variable that either holds an integer or null. Also, we have the HasValue and Value properties and the GetValueOrDefault() method to access their inner value.

With the Option type, we extend the concept of a box with possibly a value to reference types. For example, if we write Option<Movie> maybeNull, we could have a movie or "nothing."

The Option type has two subtypes: Some represents a box with a value inside it, and None, an empty box.

Optional library

C# doesn't have a built-in Option type (yet?). We have to write our own or use a third-party library. Let's bring one: the Optional library, which offers "a robust option type for C#."

With the Optional library, to create a box with a movie, we write Option.Some(anyMovie); and to create an empty box to replace null, we write Option.None<Movie>().

For example,

using Optional;
//    ^^^^^

Option<Movie> someMovie = Option.Some(new Movie("Shrek", 2001, 3.95f));
Option<int> none = Option.None<Movie>();

var shrek = someMovie.Map(value => value.Name)
                     .ValueOr("Nothing");
// "Shrek"

var nothing = none.Map(value => value.Name)
                  .ValueOr("Nothing");
// "Nothing"
Enter fullscreen mode Exit fullscreen mode

To print a movie name, we first use the Map() method. It opens a box, transforms its value, and return a new box with the result. We wrote Map(value => value.Name) to grab a movie name.

Then, we used the ValueOr() method. It works like the GetValueOrDefault() of nullable ints. If the box doesn't have a value, it returns a default value instead.

XOrNone

The Optional library has the FirstOrNone(), LastOrNone() and SingleOrNone() methods instead of the usual XOrDefault(). When there isn't a matching element, they return None instead of null.

After that quick introduction to the Option type, let's use FirstOrNone() from the Optional library instead of FirstOrDefault(),

using Optional.Collections;
//    ^^^^^

var movies = new List<Movie>
{
    new Movie("Shrek", 2001, 3.95f),
    new Movie("Inside Out", 2015, 4.1f),
    new Movie("Ratatouille", 2007, 4f),
    new Movie("Toy Story", 1995, 4.1f),
    new Movie("Cloudy with a Chance of Meatballs", 2009, 3.75f)
};

var theBestOfAll = new Movie("My Neighbor Totoro", 1988, 5);

// With Optional's FirstOrNone()
var theBest = movies.FirstOrNone(movie => movie.Rating == 5.0)
                    // ^^^^^
                    .ValueOr(theBestOfAll);
                    // ^^^^^
Console.WriteLine(theBest.Name);  // My Neighbor Totoro

record Movie(string Name, int ReleaseYear, float Rating);
Enter fullscreen mode Exit fullscreen mode

Again, we don't have a perfect movie in our catalog, FirstOrNone returns None, an empty box. Then, with the ValueOr() method, we print our favorite movie.

When we use the XOrNone() methods, we're forced to check if they return something before using their result. We didn't have to worry about null.

Voilà! Those are two alternatives to prevent NullReferenceException when working with LINQ XOrDefault() methods: using .NET 6.0 new overloads and the Option type from Functional Languages.

If you want to learn more about LINQ, check my Quick Guide to LINQ to start learning about it. Forgetting to check for null is only one of the most common mistakes when working with LINQ.


Hey! I'm Cesar, a software engineer and lifelong learner. If you want to support my work, check my courses on Educative. I have precisely one course about preventing NullReferenceException where we cover more techniques like the ones in this post.

Happy coding!

💖 💪 🙅 🚩
canro91
Cesar Aguirre

Posted on August 14, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related