A NullReferenceException (NRE) is a type of .NET exception. It occurs when a developer tries to dereference a null reference. This article covers the reasons that lead to exceptions of this type, as well as ways to prevent and fix them.
Note. This article is aimed at beginner programmers. For developers with experience, I suggest 2 activities:
Variables of reference types in C# store references to objects. To indicate that the reference does not point to an object, the null value is used. It is also worth noting that null is the default value of reference type expressions.
An exception of the NullReferenceException type occurs when you try to dereference a null reference. Examples of such operations are listed below.
Example:
Object notNullRef = new Object();
Object nullRef = default;
int hash;
hash = notNullRef.GetHashCode();
hash = nullRef.GetHashCode(); // NullReferenceException (NRE)
The code shows that two variables of the Object reference type are declared — notNullRef and nullRef:
A call to the GetHashCode method via a reference in notNullRef will work fine, as the reference refers to an object. An attempt to call the same method on nullRef will result in the CLR throwing a NullReferenceException.
Below we will look at cases where null values can come from and what operations can lead to a NullReferenceException.
Here are some examples of how a null value can get into a variable.
1. The null or default value is written explicitly.
String name = null;
var len = name.Length; // NRE
The result of the default and default(T) expression for reference types will also be null.
Object obj = default; // or default(Object)
var hash = obj.GetHashCode(); // NRE
2. Initialization of the field of the reference type by default.
class A
{
private String _name;
public void Foo()
{
var len = _name.Length; // NRE
}
}
var obj = new A();
obj.Foo();
In the example, the _name field is initialized with the default value. The _name field is null when Foo is called, so an exception will be thrown when the Length property is accessed.
3. The result of the null-conditional operator's (?.).
String name = user?.Name;
var len = name.Length; // Potential NRE
If the value of user or user.Name will be null, the null value will also be written to the name variable. In this case, accessing to the Length property without checking for null will lead to an exception.
4. The result of casting with the as operator.
Object obj = new Object();
String name = obj as String; // unsuccessful cast, name is null
var len = name.Length; // NRE
The result of the casting using the as operator will be null if the casting fails.
In the example above, the obj variable stores a reference to an instance of the Object type. An attempt to cast obj to the String type will fail, as a result of which the null value will be written to name.
5. The result of the *OrDefault method's call.
Methods of the *OrDefault (FirstOrDefault, LastOrDefault, etc.) kind from the standard library return the default value if the predicate value does not match any item or the collection is empty.
String[] strArr = ....;
String firstStr = strArr.FirstOrDefault();
var len = firstStr.Length; // Potential NRE
If there are no elements in the strArr array, the FirstOrDefault method returns the value of default(String) — null. When dereferencing a null reference, an exception occurs.
6. The boxing of default value of the Nullable<T> type.
The result of boxing Nullable<T> instances with the default value is null.
long? nullableLong1 = default;
long? nullableLong2 = null;
Nullable<long> nullableLong3 = default;
Nullable<long> nullableLong4 = null;
Nullable<long> nullableLong5 = new Nullable<long>();
var nullableToBox = ....; // nullableLong1 — nullableLong5
object boxedValue = (Object)nullableToBox; // null
_ = boxedValue.GetHashCode(); // NRE
If any of the nullableLong1 - nullableLong5 values are written to the nullableToBox variable and then boxed, the result will be null. If such a value is used without checking for null, an exception will be thrown.
The details of boxing Nullable<T> values are described in the article "Do you remember nullable value types well?".
This section lists operations whose execution with the null value results in NullReferenceException.
1. Explicit access to a member of an object.
class A
{
public String _name;
public String Name => _name;
public String GetName() { return _name; }
}
A aObj = null;
_ = aObj._name; // NRE
_ = aObj.Name; // NRE
_ = aObj.GetName(); // NRE
The same thing happens when you dereference within a method:
void Foo(A obj)
{
_ = obj.Name;
}
A aObj = null;
Foo(aObj); // NRE inside method
2. Index access.
int[] arr = null;
int val = arr[0]; // NRE
3. Calling a delegate.
Action fooAct = null;
fooAct(); // NRE
4. Iteration in foreach.
List<long> list = null;
foreach (var item in list) // NRE
{ .... }
Note that the '?.' operator won't help here:
foreach (var item in wrapper?.List) // Potential NRE
{ .... }
If wrapper or wrapper.List is null, an exception will still be thrown. This case is described in more detail in the article "The ?. operator in foreach will not protect from NullReferenceException."
5. The use of the null value as an operand for await.
Task GetPotentialNull()
{
return _condition ? .... : null;
}
await GetPotentialNull(); // Potential NRE
6. The unboxing of null values.
object obj = null;
int intVal = (int)obj; // NRE
7. The throwing an exception with a null value.
InvalidOperationException invalidOpException
= flag ? new InvalidOperationException()
: null;
throw invalidOpException; // Potential NRE
A null value can be written to the invalidOpException variable. In this case, an exception of the NullReferenceException type will be thrown.
8. Dereferencing the Target property of the WeakReference type instance.
void ProcessIfNecessary(WeakReference weakRef)
{
if (weakRef.IsAlive)
(weakRef.Target as DataProcessor).Process(); // Potential NRE
}
The reference in the WeakReference points to an object while not protecting it from garbage collection. If the object is reclaimed for garbage collection after the check of weakRef.IsAlive, but before calling the Process method, then:
9. The use of the value of the reference type's field before explicit initialization.
class A
{
private String _name;
public A()
{
var len = _name.Length; // NRE
}
}
At the time of the Length property dereferencing, the _name field is initialized with the default value (null). The result of dereferencing is an exception.
10. Unsafe call of event handlers in multithreaded code.
public event EventHandler MyEvent;
void OnMyEvent(EventArgs e)
{
if (MyEvent != null)
MyEvent(this, e); // Potential NRE
}
If the MyEvent will have no subscribers between the MyEvent != null check and the call of the event's handlers, an exception of the NullRefernceException type will be thrown.
To avoid exceptions of the NullReferenceException type, exclude the situation of null references dereference. To do this, follow the steps:
Example:
foreach (var item in potentialNullCollection?.Where(....))
{ .... }
If the value of the potentialNullCollection is null, the operator '?.' will also return null. An exception will be thrown when attempting to traverse the collection in the foreach loop.
If potentialNullCollection in this code fragment is never null, it is worth removing the '?.' operator so as not to confuse developers and code analysis tools:
foreach (var item in potentialNullCollection.Where(....))
{ .... }
If potentialNullCollection can take the null value, it is worth adding an explicit check or using the '??' operator.
// 1
if (potentialNullCollection != null)
{
foreach (var item in potentialNullCollection.Where(....))
{ .... }
}
// 2
foreach (var item in potentialNullCollection?.Where(....)
?? Enumerable.Empty<T>)
{ .... }
Note. Adding a check for null inequality is the easiest way to avoid NullReferenceException. However, sometimes such a fix will not solve the original problem, but only mask it. So when fixing code, it is useful to think about whether adding a check will be enough or whether something else needs to be fixed in the code.
In addition to the fairly obvious tip "do not dereference null references", there are several practices that will help avoid the NRE exceptions.
Without the nullable context, the null value is considered valid for reference types:
String str = null; // No warnings
Since C# 8, the language allows the use of the nullable context. It introduces the concept of nullable reference types. In the nullable context, reference types are considered to be those that do not allow null values. For example, if you use the nullable context on the code we've just looked at, the compiler will issue a warning:
String str = null; // CS8600
Warning: CS8600 Converting null literal or possible null value to non-nullable type.
The situation is the same when calling methods:
void ProcessUserName(String userName)
{
var len = userName.Length;
....
}
....
ProcessUserName(null); // CS8625
Compiler warning: CS8625 Cannot convert null literal to non-nullable reference type.
To tell the compiler that a variable of reference type can take the null value, use the '?' symbol:
String firstName = null; // CS8600
String? lastName = null; // No warning
If you try to dereference a nullable variable without checking for null, the compiler will also issue a warning:
void ProcessUserName(String? userName)
{
var len = userName.Length; // CS8602
}
Compiler warning: CS8602 - Dereference of a possibly null reference.
If you want to tell the compiler that an expression is definitely not null in a particular place in your code, you can use the null-forgiving operator — '!'. Example:
void ProcessUserName(String? userName)
{
int len = default;
if (_flag)
len = userName.Length; // CS8602
else
len = userName!.Length; // No warnings
}
Thus, the nullable context helps to write code in such a way as to minimize the possibility of dereference of null references.
There are several ways to enable the nullable context:
The nullable context has much more configuration options. We covered them in more detail in a separate article.
Note. Note that nullable context affects the compiler's warning issuing, but not the application execution logic.
String? str = null;
var len = str!.Length;
The compiler will not issue warnings for this code, since the code uses the null-forgiving operator. However, at runtime, an exception of the NullReferenceException type will occur here.
Static analyzers help find security defects and errors in code. In particular, analyzers help find the places where exceptions of the NullReferenceException type may occur.
An example of such a static analyzer is PVS-Studio.
Let's look at the example of C# code in which a NullReferenceException may occur.
private ImmutableArray<char>
GetExcludedCommitCharacters(ImmutableArray<CompletionItem> items)
{
var hashSet = new HashSet<char>();
foreach (var item in items)
{
foreach (var rule in item.Rules?.FilterCharacterRules)
{
if (rule.Kind == CharacterSetModificationKind.Add)
{
foreach (var c in rule.Characters)
{
hashSet.Add(c);
}
}
}
}
return hashSet.ToImmutableArray();
}
In the second foreach loop, developers traverse the FilterCharacterRules collection. To get the collection, they use the roslynItem.Rules?.FilterCharacterRules expression. The '?.' operator implies that the Rules property may be null. However, if the result of the expression is null, a NullReferenceException will still occur when attempting to enumerate the null value in foreach.
PVS-Studio finds this problem and issues the V3153 warning.
If items.Rules can indeed have the null value, you can protect the code from NullReferenceException with an additional check:
foreach (var item in items)
{
if (item.Rules == null)
continue;
foreach (var rule in item.Rules.FilterCharacterRules)
{
....
}
}
The analyzer will not issue a warning for fixed code.
PVS-Studio searches for various situations in code where a NullReferenceException may occur:
To use PVS-Studio to check your code, follow the steps:
Documentation on working with PVS-Studio in different environments:
0