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);
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);
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.
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
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"
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);
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!