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>
Figure 1: Person class with two compile errors
[Click on image for larger view.]
Figure 1: Person Class with Two Compile Errors

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.

Figure 2: Fixing the compile problem using property initializers
[Click on image for larger view.]
Figure 2: Fixing the Compile Problem Using Property Initializers

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.

Figure 3 - Initialize() method but compilation still fails
[Click on image for larger view.]
Figure 3: Initialize() Method but Compilation Still Fails

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.

Figure 4: Adding the 'required' keyword to the FirstName and LastName properties
[Click on image for larger view.]
Figure 4: Adding the 'required' Keyword to the FirstName and LastName Properties

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.

Figure 5: The 'required' keyword means you must supply values when you construct the class
[Click on image for larger view.]
Figure 5: The 'required' Keyword Means You Must Supply Values when You Construct the Class

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.

Figure 6: The Compiler doesn't know that Initialize() actually initializes
[Click on image for larger view.]
Figure 6: The Compiler Doesn't Know that Initialize() Actually Initializes

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.

Figure 7: The [MemberNotNull] Attribute Fixes the Compile Problem
[Click on image for larger view.]
Figure 7: The [MemberNotNull] Attribute Fixes the Compile Problems

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


Keep Up-to-Date with Visual Studio Live!

Email address*Country*