Why Null?
By Chinthaka Chandrasekara“Object reference not set to an instance of an object.” Any C# developer, be it a newbie, an intermediate or an expert, would have encountered this error at some point during their coding career, and subsequently it would have been the cause of a lot of hours of frustration and hair-pulling.
This infamous and dreaded error message appears when you get a Null Reference Exception, one of the most popular exceptions you can encounter in C# .NET. In simple terms, this exception is thrown when you try to access a variable or an instance which at that particular time holds a null reference.
So, we’ve all seen this error, but what does it actually mean to have a null reference? And how does it differ from a non-null reference?
What is it?
The Null Reference Exception is arguably the most common exception that any C# .NET developer would encounter, and it is statistically responsible for the majority of bugs that we find in application code. But in order to understand what it means, we first have to understand what we mean by ‘reference’. In C# .NET we can divide data types to two main categories: value types and reference types. A value typed variable will store its value as the value itself. In contrast, reference typed variables hold a reference to a particular place in memory where the object containing the value of the variable would live. We usually define primitive types of the programming language as value types, while classes would be defined as reference types.
This difference between types is important, because while a value typed variable would always have a default value at hand, a reference typed variable (if the reference is not properly set) could essentially be pointing to nothing. This is exactly what we mean by a null reference – a reference that does not point to an object, but rather points to ‘nothing’. When this happens, and you try to use that particular variable, either to get its value or use any of its members (fields or methods), you get a Null Reference Exception.
int example; //example = 0
public class Example {}
Example example; //example = null
How can it happen in code?
So how does the Null Reference Exception manifest itself in code?
The first thing to understand is how an object can be null. An object reference could equate to null due to only two reasons: either you set it to null, or you never set it to anything at all, and hence it’s value by default will be null (since object variables that are uninitialized would point to nothing). The former is much more dangerous than the latter, in that the developer is using null intentionally to indicate there is no meaningful value available. Note that since C# uses the concept of nullable datatypes for variables (denoted by the ‘?’ icon), you can assign null to them to indicate there is no value stored in it. The bottom line is that you should never assign an object to null, unless you have no other option. If you are doing it just to avoid some syntactical error, there’s a good chance that there is something wrong with the architecture and/or branching of your code, and hence it would be better to go through the logic again to correct any such flaws.
The next thing to note is that null acts the same as any other value you give to objects (i.e. you can pass it around just like any other value). If you create an object A, do not initialize it, and then equate another object B to object A, the value of object B would also be null, regardless of whatever value it previously would have had. Also, if you pass object A from one method to another, the null reference would not change, and the object would be null regardless of which method and whatever scope it is in.
With that being said, let’s now take a look at some practical examples within code where a Null Reference Exception would most likely occur:
Direct reference
A direct reference to an underlying field of an object could result in a null reference if the parent object is null.
e.g. -
HttpContext.Current.User.Identity.Name
If either of HttpContext, Current, User or Identity is null, the value of the above expression is unable to be computed, and hence a null reference exception is raised.
We can find out where the null reference happens by deconstructing and re-writing the expression to a simpler form:
var ref1 = HttpContext;
var ref2 = ref1.Current;
var ref3 = ref2.User;
var ref4 = ref3.Identity;
ref4.Name
Indirect reference
An indirect reference with one complex object having another underlying object field could result in a null reference if the parent object is initialized but the underlying object is not.
e.g. -
public class User
{
public Address Address { get; set; }
}
public class Address
{
public string Street { get; set; }
}
public class Execute
{
public void CreateUser()
{
User u1 = new User();
string streetName = u1.Address.Street;
}
}
In the above example, the User object was initialized, but the Address object field was not. Hence, there is no Address to get a Street from. To avoid this, we should initialize the Address field before reading it. This could even have been done in the parent (User) object’s constructor.
Nested Types
When we nest objects, or even collections, we can encounter null references due to the nested type not being initialized.
e.g. – When nesting objects:
User u1 = new User
{
Address = { Street = “Anderson road” }
};
This translates to:
User u1 = new User();
u1.Address.Street = “Anderson road”;
While the new keyword is used, it only creates a new instance of User, but not a new instance of Address, so the Address property is still null.
When nesting collections:
public class User
{
public ICollection<Address> Addresses { get; set; }
}
public class Address
{
public string Street { get; set; }
}
User u1 = new User
{
Addresses = {
new Address { Street = "Anderson road" },
new Address { Street = "Robinson road" },
}
};
This translates to:
User u1 = new User ();
u1.Addresses.Add(new Address { Street = "Anderson road" });
u1.Addresses.Add(new Address { Street = "Robinson road" });
This creates an instance of User, but the Addresses collection is still null. Hence when we try to add new elements to the collection, we encounter that null reference which raises an exception. Nested fields that are collections should always be initialized to empty collections before using them.
Collections
Collections themselves encounter null references in various instances:
e.g. -
int[] numbers = null;
int n = numbers[0];
The numbers array is null. Hence there is no actual array to index.
User[] users = new User[5];
users[0].Age = 20
The users[0] reference is null. The array was allocated but not initialized. There is no User to set the Age for.
long[][] array = new long[1][];
array[0][0] = 3;
Only the first dimension of this two-dimensional array has been initialized. We need to use array[0] = new long[2]; first.
Dictionary<string, string> userNames = null;
string name = userNames["Tom"];
The userNames Dictionary is null and yet to be initialized. Hence there is no Dictionary to perform the lookup operation.
public class User
{
public string Name { get; set; }
}
var users = new List< User >();
users.Add(null);
var names = from u in users select p.Name;
string firstName = names.First();
An exception is thrown here, but actually occurs the line above. “u” is actually null because the first element we added to the list is null.
public class User
{
public string Name { get; set; }
}
List<User> users;
foreach(var user in users)
{
//Some code
}
The foreach loop throws a null reference exception when you try to iterate on an initialized collection. This is usually caused by an unexpected null result from methods that return collections.
Events
C# events and event handlers sometimes causes null reference exceptions to occur.
e.g. -
public class Demo
{
public event EventHandler StateChanged;
protected virtual void OnStateChanged(EventArgs e)
{
StateChanged(this, e);
}
}
An exception will be thrown when StateChanged is called if no event handlers have been attached to the StateChanged event.
Casting
var newObj = oldObj as Object;
If the above cast is invalid, it does not throw an Invalid Cast Exception but rather returns null when the cast fails (or when oldObj is itself null).
LINQ
When dealing with LINQ queries, methods like First() and Single() throw exceptions when there is nothing which satisfies the given condition. In contrast, methods like FirstOrDefault() and SingleOrDefault() return null instead, which can result in null references if not checked for properly.
How can we avoid it?
There are quite a few ways in which we can implement within code to avoid or handle null reference exceptions. The key rule to follow when handling null reference exceptions is that it should never ever be seen by users. Also, remember to optimize your code for the convenience of the caller, not the convenience of the author.
The most obvious way to deal with null reference exceptions is to ALWAYS initialize your variables with valid values before you actually use them. However, there are also some other ways in which you can avoid the propagation of this error:
Explicitly check for null
When you expect the reference of an object to sometimes be null, you can check for it being null before accessing its instance members. Once this check is in place, you can handle it in few different ways:
- Ignore null values
e.g. -
public void PrintName(User u)
{
if (u != null)
{
Console.WriteLine(u.Name);
}
}
- Provide a default value
e.g. -
public string GetName(User u)
{
if (u == null)
return "Unknown";
return u.Name;
}
- Throw a custom exception
e.g. -
public string GetName(string userId)
{
var user = users.FindUser(userId);
if (user == null)
throw new UserNotFoundException(userId);
return user.Name;
}
Debug.Assert
When you know during development that a method could, but never should return null, in Debug mode you can use Debug.Assert() to break as soon as possible when it does occur:
e.g. -
public string GetName(int id)
{
var user = users.FindUser(id);
Debug.Assert(user != null, "User not found.");
return user.Name;
}
However, this check will not end up in your release build, causing it to throw the Null Reference Exception again when user == null at runtime in release mode.
GetValueOrDefault()
When using nullable types, we can use the GetValueOrDefault method to provide a default value when objects are null.
e.g. -
DateTime? dateTime = null;
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
dateTime = new DateTime(2021, 08, 31);
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
The first Console.WriteLine will display the default value provided (DateTime.Now), because the value of dateTime will be null. The second Console.WriteLine however will display the correct value of dateTime, not the default.
The null condition operator
In the latest versions of C#, the null conditional operator was introduced. This provides a neat little shorthand for doing null checks, instead of having numerous explicit null checks throughout your code. Also known as the safe navigation operator, you can simply use the ‘?’ icon in your code and it will stop mid-statement and return null instead of throwing the exception. (i.e. if the expression on the left side of the operator is null, then the right side will not be evaluated, and null is returned instead.)
e.g. –
What would earlier be written like this:
var name = user.Name == null ? null : person.Name.ToUpper();
would now be written as,
var name = user.Name?.ToUpper();
where name would set to null if user.Name is null.
Also, the null conditional operator can be used with collections as follows:
int? length = userList?.Length;
Customer first = userList?[0];
int? count = userList?[0]?.Orders?.Count();
Each of these expressions would evaluate to null if userList was null, or in the last case, if userList, the first user, or Orders was null.
The null coalescing operator
Another great addition to later versions of C#, the null coalescing operator provides a shorthand method to provide a default value, should an object be null. This is in contrast to the null condition operator, which simply checks for the null state in objects. The null coalescing operator is denoted by the ‘??’ symbol, and works with all nullable data types.
e.g. -
List<string> userNames = null;
foreach (var name in userNames ?? new List<string>())
{
Console.WriteLine(name);
}
When the userNames list is null, the list will be initialized to an empty list before executing the loop.
Null context
Introduced in C# 8, null contexts and nullable reference types perform static analysis on variables and provide a compiler warning if a value can potentially be null or have been set to null. The nullable reference types basically allows certain types to explicitly be null, and is denoted using the same syntax as nullable value types: a ‘?’ is appended to the type of the variable.
The nullable annotation context and nullable warning context can be set for a project using the Nullable element in your csproj file. This element configures how the compiler interprets the nullability of types and what warnings are generated.
In conclusion
So, in conclusion to everything that has been discussed, it is clear that the Null Reference Exception in C# .NET code can occur due to a number of reasons. However, bear in mind that there could be many more causes for this exception than what has been mentioned above. In some cases when using external tools and libraries, the cause of a null reference could even be specific to the internal workings of that particular tool or library, and hence you need to be careful on how you handle it. A good rule of thumb to always have in your mind as a C# programmer is:
“If it can be null, it will be null”
That being said, the good thing about this type of exception is that in a lot of instances where it occurs, there are many simple ways to fix or avoid it, again as mentioned above. By simply adding a few more lines of logic to ensure that our objects are not null before we try to use them, we can avoid null references altogether.
Also note that this type of exception is not particular to C# .NET at all. Most programming languages, especially ones that use the object-oriented paradigm, use the concept of objects to store data, and hence will be susceptible to null references. The concept of null is itself universal to the programming world, since almost all code uses it. The important thing is that as developers, we should always assume that any object can be null, and write code in a defensive manner, without creating loopholes through which null objects could pass. Always assume that calls to databases or other external APIs will fail, assume every variable you use will have invalid data, and make sure you understand what the code, and in turn the application, will do when that happens. Proper exception handling is a critical aspect of any enterprise level application, and the ability to perform exception handling efficiently and gracefully is the hallmark of a good software programmer.
Always remember - code safe, and stay safe!