Time for another post in the series Debugging common .NET exceptions. Today's exception is, without a doubt, the error most people have experienced: System.NullReferenceException
. The exception happens when you try to invoke a reference that you were expecting to point to an object but in fact, points to null
. Let's get started!
Handling the error
There are some clever ways to avoid a NullReferenceException
, but before we start looking into those, let us see how the exception can be caught. Being a plain old C# exception, NullReferenceException
can be caught using a try/catch
:
try
{
string s = null;
s.ToString();
}
catch (NullReferenceException e)
{
// Do something with e, please.
}
Running the code above will produce the following error:
System.NullReferenceException: Object reference not set to an instance of an object.
Debugging the error
We already know why the exception is happening. Something is null
. When looking at the code above, it's clear that s
is null and the stack trace even tells us that:
Sometimes spotting what is null
can be hard. Take a look at the following example:
var street = service.GetUser().Address.Street;
If the code above throws a NullReferenceException
, what is null
? service
? The result of GetUser()
? Address
? At first glance, Visual Studio isn't exactly helpful either:
There is a range of different ways to find out what is going on. Let's look at the most commonly used ones.
Splitting chained method-calls to multiple lines
Spotting which call that caused an error is a lot easier if the calls are split into multiple lines:
var service = new Service();
var user = service.GetUser();
var address = user.Address;
var street = address.Street;
Running the code reveals the actual call causing the exception:
In the example above user.Address
returns null, why address.Street
causes the NullReferenceException
.
While splitting code into atoms like this can help debug what is going wrong, it's not preferable in terms of readability (IMO).
Using Null Reference Analysis in Visual Studio
If you are on Visual Studio 2017 or newer (if not, now is the time to upgrade), you will have the Null Reference Analysis feature available. With this in place, Visual Studio can show you exactly what is null. Let's change the example back to method-chaining:
var street = service.GetUser().Address.Street;
To enable the analysis go to Debug | Windows | Exception Settings. Check Common Language Runtime Exceptions (if not already checked) or extend the node and check the exceptions you are interested in. In this case, you can check System.NullReferenceException. When running the code, the debugger breaks on the NullReferenceException
and you now see the Exception Thrown window:
Voila! The window says "ConsoleApp18.User.Address.get returned null". Exactly what we wanted to see. This will require you to run the code locally, though. If you are experiencing the exception on your production website, the Null Reference Analysis will not be available, since this is a feature belonging to Visual Studio (unfortunately). With that said, you can attach a debugger to a remote site running on Azure as explained here: Introduction to Remote Debugging on Azure Web Sites.
Fixing the error
There are various ways to fix NullReferenceException
. We'll start with the simple (but dirty) approach.
Using null checks
If null
is an allowed value of an object, you will need to check for it. The most simple solution is to include a bunch of if
-statements.
if (service != null)
{
var user = service.GetUser();
if (user != null)
{
var address = user.Address;
if (address != null)
{
var street = address.Street;
}
}
}
The previous code will only reach address.Street
if everything else is not null
. We can probably agree that the code isn't exactly pretty. Having multiple nested steps is harder to read. We can reverse the if
-statements:
if (service == null) return;
var user = service.GetUser();
if (user == null) return;
var address = user.Address;
if (address == null) return;
var street = address.Street;
Simpler, but still a lot of code to get a street name.
Using null-conditional operator
C# 6 introduced a piece of syntactic sugar to check for null
: null-conditional operator. Let's change the method-chain example from before to use the "new" operator:
var user = service?.GetUser()?.Address?.Street;
The ?
to the right of each variable, corresponds the nested if
-statements from previously. But with much less code.
Use Debug.Assert during development
When getting a NullReferenceException
it can be hard to spot the intent with the code from the original developer. Rather than including if
-statements, it can be clearer for future authors of your code to use the Debug.Assert
-method. Much like in a xUnit or NUnit test, you use Assert
to verify the desired state on your objects. In the example from above, the service
object could have come through a parameter or a constructor injected dependency:
class MyClass
{
Service service;
public MyClass(Service service)
{
this.service = service;
}
public string UserStreet()
{
return service.GetUser().Address.Street;
}
}
To make a statement in your code that service
should never be allowed to have the value of null
, extend the constructor:
public MyClass(Service service)
{
Debug.Assert(service != null);
this.service = service;
}
In the case MyClass
is constructed with null
, the following error is shown when running locally:
Use nullable reference types in C# 8.0
When designing code you often end up expecting parameters to be not null
but end up checking for null
to avoid a NullReferenceException
. As you already know, all reference types in C# can take the value of null
. Value types like int
and boolean
cannot take a value of null
unless explicitly specified using the nullable value type (int?
or Nullable<int>
). Maybe it should have been the other way around with reference types all along?
C# 8 can fix this with nullable reference types (maybe NOT nullable reference types are a better name). Since this is a breaking change, it is launched as an opt-in feature. Nullable reference types are a great way to avoid NullReferenceException
since you are very explicit about where you expect null
and where not.
To enable not nullable reference types, create a new .NET Core 3 project and add the following to the csproj
file:
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
You should be on C# 8 already, but to make it explicit, I've added the LangVersion
element. The Nullable
element enables nullable reference types. Out of the box, C# 8 creates a warning if it identifies the use of null
where a value is expected. Let's see how that looks:
class Program
{
static void Main()
{
new Program().SayHello(null);
}
public void SayHello(string msg)
{
Console.WriteLine(msg);
}
}
When compiling we see the following warning:
Program.cs(9,36): warning CS8625: Cannot convert null literal to non-nullable reference type. [C:\projects\core3\core3.csproj]
I know you are not one of them, but some people developed a warning resistance which means that warnings are simply ignored. To overcome this, make sure that errors like this causes build errors over warnings by adding the following to csproj
:
<WarningsAsErrors>CS8602,CS8603,CS8618,CS8625</WarningsAsErrors>
This will tell the C# compiler to treat these four nullable reference type warnings as errors instead.
Just to make it clear, allowing null in the msg
parameter, you use the ?
characters as with value types:
public void SayHello(string? msg)
{
...
}
Logging and monitoring
Logging and monitoring for null reference exceptions are a must. While some developers tempt to create control flow from exceptions (no one should), null reference exceptions should never happen. This means that a System.NullReferenceException
is a type of exception that should always be logged and fixed. A null check through either an if
statement or the null-conditional operator is always the preferred way of handling potential null values. But make sure to implement a logging strategy that logs all uncaught exceptions, including the System.NullReferenceException
.
When logging a System.NullReferenceException
in a log file, database, elmah.io, or similar, it can be hard to spot what is null. You typically only see the method-name that causes the NullReferenceException
and the Null Reference Analysis feature is only available while debugging inside Visual Studio. I will recommend you to always Include filename and line number in stack traces. This will pinpoint the exact line where the error happens.
Would your users appreciate fewer errors?
elmah.io is the easy error logging and uptime monitoring service for .NET. Take back control of your errors with support for all .NET web and logging frameworks.
➡️ Error Monitoring for .NET Web Applications ⬅️
This article first appeared on the elmah.io blog at https://blog.elmah.io/debugging-system-nullreferenceexception-object-reference-not-set-to-an-instance-of-an-object/
Top comments (0)