Keeping the Compiler Happy: Null Member Variable Compile Errors in C#
Wait! This won't be boring! Yes, I know -- this probably isn't the most exciting topic ever, but I ran into some problems this week and I actually learned some cool stuff about nullability in C# and the compiler.
The issue I was running into was member variable initialization inside of a class. WAIT! Please don't quit on this article! I swear it gets better! I started off with a simple class, but it had complex initialization logic that involved multiple method calls and a bunch of calculations. Simple enough but I couldn't get it to compile because I kept getting nullability errors from the compiler.
Stick with me and I'll talk you through the fixes I used to keep the compiler happy.
Simple Class. Complex Logic.
The sample code I'm going to use is going to be much simpler, but it'll demonstrate the problems. Let's start with a C# class for a Person that has two string properties: FirstName
and LastName
. Looking at the code below, right from the very beginning I've got two compilation errors on those properties. Since strings are null by default, this is failing compilation because I'm not initializing the property values.
div>
Now it would be simple enough in this case to just set the values either using a constructor or by using a property initializer. Just tag "= string.Empty;" onto the end of each of those properties and the compiler warning goes away.
This works in a simple example like this, but in my production application code, the logic wasn't so straightforward -- and I needed to use the initialization logic in multiple places.
Use an Initialization Method
Since I needed code reuse and wanted avoid duplicate code, my ideal solution is to use an Initialize()
method that I call from the constructor.
The problem is that once I do this, I'm getting a compile error.
Non-nullable property 'FirstName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. CS8618
This is super annoying because I know as the programmer that the Initialize()
method actually is fixing the initialization problem and therefore the compile error is kind of wrong. It's not totally wrong ... but it's pretty much wrong. The problem is that I don't have a way to prove it to the compiler.
The C# 'required' Keyword: Make it Someone Else's Problem
The compile error is helpfully giving us a couple of options. Either make the property nullable or add the required
keyword. Both of these "solutions" solve the problem without solving the problem -- arguably they just move the problem somewhere else and make it someone else's problem.
If we make the property nullable, we get properties that look like this:
public string? FirstName { get; set; }
That "?" at the end of the string type means that anyone who uses this property now has to worry that the value could be null. That spreads a lot of null-checking logic throughout the app and that stinks. Kind of misses the point of having compiler null checks, too.
The other suggestion is to use the required
keyword. This was something that I didn't know about. It was added as part of C# 11 back in November of 2022 so it's still a somewhat new feature in the language.
If you add the required
keyword to the properties, it means that the caller to this class must supply a value as part of the call. Put another way, anyone who new's up an instance of Person
would need to supply the values for FirstName
and LastName
.
This solves the compile problem in Person.cs but -- like I said -- it kind of moves the problem somewhere else. Let's say I have a class called PersonService
that creates an instance of Person
. When PersonService
creates that instance of Person
(see Figure 5), it needs to pass values for FirstName
and LastName
. Once again, in a simple scenario like this, it isn't all that onerous -- but if the logic for initialization is complex, this is a recipe for sprawling code duplication.
This moves the problem out of the Person
class and potentially spreads the problem around in the rest of the code. This violates not only best practices of object-orientation but also guidelines for maintainability.
Not my favorite solution.
Show the Compiler Who's Boss: The [NotNullWhen] Attribute
Let's go back to that Initialize()
method from a little while back. In my actual production application, I had complex initialization logic that I needed to use in a couple of different places and that made this Initialize()
method my preferred option. But it also caused problems because the compiler didn't officially know that I was actually indeed initializing the FirstName
and LastName
property values.
The solution here is to add an attribute called [MemberNotNull]
. It's part of the System.Diagnostics.CodeAnalysis
namespace and what it does is tell the compiler what we're achieving in the Initialize()
method -- in this case, actually setting FirstName
and LastName
to non-null values.
In the code sample below (Figure 7), I apply the [MemberNotNull]
attribute and pass it the names of the FirstName
and LastName
properties. Once I do that, I don't have any compiler errors complaining about null values and initialization and I also get to reuse my Initialize()
method code. Just by adding this attribute, it saves me from having to pile all my initialization logic into my constructor just to keep the compiler happy.
By the way, there are also some other nullability hint attributes that you might want to check out: [NotNull]
, [NotNullWhen]
, and [MemberNotNullWhen]
.
Summary
Hopefully, you agree that this wasn't that boring and hopefully you learned something. If you care about your compile time null checks (and you should), you sometimes have to get creative about your initialization logic. You can either pile all your init code into the constructor so that the C# compiler understands the logic by default, you can add the required
keyword to mandate that callers pass you the required values, or you can tell the compiler to chill out by using [MemberNotNull]
.
About the Author
Benjamin Day is a consultant, trainer and author specializing in software development, project management and leadership.
Posted by Benjamin Day on 11/18/2024