In this post, we will explore through the land of null references, how to guard and work with them in C# 8.
As I probably mentioned in some of my older posts, I’m a big fan of code restrictions that can be imposed by the IDE. I think this is a reason why stylecop was/is so popular (I don’t know since I mostly use Rider nowadays and Resharper for Visual Studio).
Because of this, whenever I start a new project, I always turn on the feature to treat warnings as errors, just to get them out of the way from the start.
With the addition of the null reference in C# 8, I turn on this feature as well, so that if I need to use a null
, it will be out of an actual thought out decision and not by default, this in combination with the “warnings as errors” option, doesn’t even let me compile without writing my code thoughtfully in regards to null
s.
What Is a Null Reference?
The link mentioned above goes into a lot more detail about it, but suffice to say that we have two new symbols to use in conjunction with reference types.
The Usage of ? at Declaration
In the past, only the value types such as custom structs or the base types like int
, bool
, double
, etc. benefitted from having a default value and as such if we wanted a nullable variant, we needed to prefix the type definition with a ?
which is some syntactic sugar sprinkled over the fact that when the code compiles, we would get a Nullable<T>
, where T
was of type struct
, which would also give us access to the HasValue
and Value
properties to check if the variable is null
.
In the case of references, there is no such background magic, but the compiler is smart enough now to know that in a non-nullable scenario (either by turning it on, for a section of code, a file or the whole project), that the reference variable (or field or property) cannot receive a null
value and as such, it will highlight a warning or throw an error. This also means that because this is not a form of syntactic sugar to express something different like Nullable<T>
, that we won’t have access to the HasValue
and Value
properties.
Since there is no point in a property for the value because the value is the variable itself, for those that was something akin to what Nullable<T>
for reference variables declared as nullable, we could use an extension method, might not be as pretty but it does the job. Here’s an example of such an extension:
public static class NullableObjectExtensions
{
public static bool IsNull<T>(this T objectInstance) where T : new()
{
return objectInstance is null;
}
public static bool IsNotNull<T>(this T objectInstance) where T : new()
{
return !objectInstance.IsNull();
}
}
I like to have both of them just so that I don’t need to look for the not
operator, and it makes it easier to read.
The Usage of !for When We Know Something Isn’t Null
The !
operator is called the null
-forgiving operator. So if we’re in a situation in which we know that something shouldn’t be null
but we’re still getting errors, then we could postfix our variable with the a !
and the compiler will just ignore that check.
The need for this mostly comes from a shortcoming of the compiler, in that if we do have a nullable variable that is being checked by the IsNotNull
extension method, even if the variable is not null
, we will still get the warning/error, because the compiler doesn’t follow the check we have in a different method, so we have to tell the compiler “yes I know there’s a risk of a null
reference, but I did just check it”.
One thing to note is that the compiler is smart enough to recognize if we checked for a null
reference if it’s in the same method.
In the following code snippet, I will show when we will use the null
-forgiving operator, when not, and another type of trick using pattern matching and the null-conditional operator.
class Program
{
static void Main(string[] args)
{
MyClassA a = new MyClassA();
if (a.MyClassB.IsNotNull())
{
a.MyClassB.Prop.ToString();
a.MyClassB!.Prop.ToString();
}
if (a.MyClassB != null)
{
a.MyClassB.Prop
.ToString();
}
if (!(a.MyClassB is null))
{
a.MyClassB.Prop
.ToString();
}
if (a.MyClassB is {})
{
a.MyClassB.Prop.ToString();
}
}
}
class MyClassA
{
public MyClassB? MyClassB { get; set; }
}
class MyClassB
{
public string Prop { get; set; } = string.Empty;
}
public static class NullableObjectExtensions
{
public static bool IsNull<T>(this T objectInstance) where T : new()
{
return objectInstance is null;
}
public static bool IsNotNull<T>(this T objectInstance) where T : new()
{
return !objectInstance.IsNull();
}
}
Now that we saw how we can use null
reference, check for them with the help of the compiler, it’s time to return to the topic at hand, and that is to use an alternative to null
checks by using the Null-Object pattern.
Moving to Null-Object Pattern
Likewise, I will not go into great detail as to what this pattern is because there are a lot of great resources about design patterns that explain them.
To use this, we need to create a static
field that will give us a default object.
Here is an example of the modifications in place to use the Null
object.
class MyClassA
{
public MyClassB MyClassB { get; set; } = MyClassB.None;
}
class MyClassB
{
public static MyClassB None { get; } = new MyClassB();
public string Prop { get; set; } = string.Empty;
}
By creating a static
property that can only be read and with a default instance, we can start writing our code free of null
checks, better yet since it’s a static
, if we want to check if the field has been filled in with a proper instance, we just need to compare it to the None
property since they will all use the same reference. So if we can have the following:
MyClassA a = new MyClassA();
if (a.MyClassB == MyClassB.None)
{
}
Of course, there is a downside to this and that is since they are all using the same reference, if we do change something on the None
instance, then the change will be accessible everywhere that reference is being used.
So a few solutions are as follows:
- Write your classes with encapsulation and immutability in mind (easier said than done), that would encourage constructor parameters. Of course, by using this approach, then we are limited when using POCO classes for deserialization like for example from JSON, this could still work if deserializers can use reflection for setting
private
fields. - Do a hackish approach that is kinda ugly from my point of view but at least it works, and it’s better if it’s ugly rater than crash in production (time will tell if there is a better approach).
This is my hackish solution for now:
void Main()
{
MyClassA a = new MyClassA();
Console.WriteLine(a.MyClassB.Prop);
a.MyClassB.Prop = "456";
}
class MyClassA
{
public MyClassB MyClassB { get; set; } = MyClassB.None;
}
class MyClassB
{
private string _propValue = string.Empty;
public static MyClassB None { get; } = new MyClassB {
Prop = "123"
};
public string Prop
{
get { return _propValue; }
set
{
if (this == None)
{
throw new InvalidOperationException($"Cannot set {nameof(Prop)}
on instance of {nameof(None)}");
}
_propValue = value;
}
}
}
The obvious downside to this approach is that you need a check for each property that gets exposed to changes.
Conclusion
I hope you enjoyed this exploration through the land of null
references, how to guard, and work with them.
I also hope that my hackish approach will find a better form, maybe one day when Rosylin will support AOP (we can all dream right? ).
See you next time, and happy coding.