Over the last 24 years, C# has evolved a lot. Think about all the essential C# language features that have been added: generics (C# 2.0), object and collection initializers (C# 3.0), named and optional function arguments (C# 4.0), and async await (C# 5.0), not to mention extension methods, tasks, lambda expressions, anonymous types, auto-properties, tuples, and pattern-matching switch expressions. All of these were introduced after C# 1.0 and all of them have made C# better.
In 2019 (C# 8.0), however, Microsoft introduced a C# feature that has not been so universally loved. In fact, it has generated a fair amount of confusion, debate, and outright dissent. I'm talking about Nullable Reference Types. In this article, I want to tackle this thorny and controversial topic, see what it's all about, and help you decide if and how you might want to use it.
The Purpose of Null Reference Types
In a nutshell, the purpose of enabling Null Reference Types is to help prevent Null Reference Exceptions. You're probably very familiar with these, but I want to review how they come about and how programmers normally avoid them.
Null Reference Exceptions
A Null Reference Exception happens when you have a variable that is null and then you try to use it as if it weren't null. Here's an example of some code that could generate a Null Reference Exception:
class Polygon
{
int Sides { get; set; }
static void HelloTriangle(Polygon p)
{
// this is not safe, p might be null
if (p.Sides == 3)
{
Console.WriteLine("It's a triangle");
}
}
}
The problem with this code is that it dereferences p.Sides without first checking to see if the variable p is null or not. If p happens to be null, this will cause a Null Reference Exception at runtime, as shown in Figure 1.

The way to correct this is to check p's null status before dereferencing it, like this:
// this is safe
if (p != null && p.Sides == 3)
{
Console.WriteLine("It's a triangle");
}
// this is safe, too, using null-conditional syntax
if (p?.Sides == 3)
{
Console.WriteLine("It's a triangle");
}
Here's another example:
static string ReplaceAmpersands(string s)
{
// this is not safe, s might be null
return s.Replace("&", "and");
}
In C#, strings are reference types, so the problem, again, is that s might be null so calling Replace could generate a Null Reference Exception. Here are some safe versions of the function:
// this is safe
if (string.IsNullOrEmpty(s))
{
return string.Empty;
}
else
{
return s.Replace("&", "and");
}
// this is safe, too
return s?.Replace("&", "and") ?? string.Empty;
Most programmers learn early to write defensive code in order to avoid Null Reference Exceptions. But still, Null Reference Exceptions happen. Wouldn't it be nice if the compiler could somehow check for these and at least provide a warning if the code might be unsafe? That's what the Nullable Reference Types feature attempts to do.
Using Static Analysis to Spot Possible Null Reference Exceptions
The term “Static Analysis” or “Static Flow Analysis” refers to the process of examining source code without executing it to find potential bugs and other problems like security vulnerabilities, code quality issues and violations of coding standards. The C# compiler does lots of static analysis as you work in Visual Studio. For example, if you have a variable that's assigned but never used, it will be underlined with a squiggly green line and you get a compiler warning that says, “CS0219: The variable x is assigned but its value is never used,” as shown in Figure 2.

This is an example of static analysis finding a possible issue and giving a warning. The code still compiles, but the compiler is indicating that something could be a problem. Other examples of static analysis generating warnings include:
- CS1062: Unreachable code detected
- CS1998: Async method lacks “await” operators and will run asynchronously
- CS0649: The field
MyClass.myFieldis never assigned to and will always have its default value.
You have probably seen these compiler warnings and others like them many times. So here's the question: Why can't the C# compiler use static analysis to warn about possible Null Reference Exceptions? This seems simple enough. The problem is that although there are many cases where ReferenceType variables could be null and therefore should be checked, there are also many cases where ReferenceType variables are pretty much guaranteed to never be null and do not need checking.
In the first example above, the variable p could very well be null and therefore needs checking:
// p could possibly be null
Polygon p = shapes.Find(s => s.Sides == 8);
int i = p.Sides + 1; // this is not safe
And here's an example of a variable that will never be null and therefore does not need a null check:
Polygon p = new()
{
Sides = 3
};
int i = p.Sides + 1; // this is safe
Here's another example, in this case a string, where a Reference Type will never be null:
// string.Concat never returns null
string s = string.Concat("Hello ", null);
s = s.ToUpper(); // this is safe
The fact that reference type variables come in these two flavors, could-be-null and will-never-be-null, creates a problem for static analysis: If it warns on every single instance where a reference type is used without a null check, it may produce false positives for all the will-never-be-null cases. This noise could drown out the true signal of legitimate warnings and make the analysis frustrating and unhelpful.
Up until C# 8.0, C# took the approach that the risk of these false positives was too high, so the compiler did not provide warnings about possible Null Reference Exceptions. The burden of finding possible Null Reference Exceptions fell purely on the programmer.
Helping the Compiler with Annotations
For the C# compiler to warn on possible Null Reference Exceptions, it needs some help: It needs additional information about the flavor of the reference type variable. Is the variable one that could-be-null and needs checking? Or is it one that will-never-be-null and therefore is considered safe? Turning on Nullable Reference Types allows you to give the compiler the help it needs: It enables a bit of C# syntax that allows you to annotate reference type variables as could-be-null or will-never-be-null. This is done using the ? symbol as a post-fix on the variable name. If a Reference Type variable has the ? annotation, the compiler considers it could-be-null. If it lacks the ? annotation, the compiler treats it as will-never-be-null.
// example of a will-never-be-null variable
Polygon p = new() { Sides = 3 };
// example of a could-be-null variable
Polygon? p = shapes.Find(s => s.Sides == 8);
// example of a will-never-be-null variable
string s = string.Concat("Hello ", null);
// example of a could-be-null variable
string? s = sqlDataReader.GetString(0);
With this new information, the C# compiler can now proceed to analyze the code and generate more reliable warnings about possible Null Reference Exceptions:
Polygon p = new() { Sides = 3 };
int i = p.Sides + 1; // no compiler warning
Polygon? p = shapes.Find(s => s.Sides == 8);
int i = p.Sides + 1; // compiler warning
string s = string.Concat("Hello ", null);
s = s.Replace("&", "and"); // no compiler warning
string? s = sqlDataReader.GetString(0);
s = s.Replace("&", "and"); // compiler warning
I encourage you to stop here for a moment and really take this in. Note how the different variable declarations, Polygon vs. Polygon? and string vs. string?, allow the compiler to know when to warn and when not to warn. This is the secret sauce that makes the Nullable Reference Types feature work.
Annotations are the secret sauce that make the Nullable Reference Types feature work.
Not the Same as Nullable Value Types
One very important point to be clear about is that Nullable Reference Types are not analogous to Nullable Value Types. One might think that string is to string? as int is to int?, but this is not true. Under the hood, a Nullable Value Type like int? is an instance of the generic struct Nullable<T>, which means that int and int? are distinct types. But under the hood, string and string? are actually the same type. You can verify this using reflection. Imagine you have this class:
public class MyClass
{
public int NormalInt;
public int? NullableInt;
public string NormalString;
public string? NullableString;
}
Now run this function to print out the name and type of the field in your class:
FieldInfo[] fields = typeof(MyClass).GetFields();
foreach (FieldInfo field in fields)
{
Debug.WriteLine($"Field {field.Name} - " +
$"Type {field.FieldType.Name}");
}
The output is:
Field NormalInt – Type Int32
Field NullableInt – Type Nullable`1
Field NormalString – Type String
Field NullableString – Type String
As you can see, string and string? are the same type while int and int? are not. When I first realized this, I was quite surprised, and it left me with this question: If adding the ? annotation doesn't change the type of a variable, what does it do?
Nullable Reference Types are not analogous to Nullable Value Types and the
?annotation does not change the underlying data type.
It turns out that adding the ? annotation on a Reference Type variable adds a custom attribute to the variable. This custom attribute is called NullableAttribute and it's defined in the System.Runtime.CompilerServices namespace. You can use reflection to locate this custom attribute by name and see its value.
Attribute? attr =
field.GetCustomAttributes().
FirstOrDefault(a => a.GetType().FullName.
EndsWith("CompilerServices.NullableAttribute");
FieldInfo? field = nullAttr?.GetType().GetField("NullableFlags");
if (field != null)
{
byte[] flags = field.GetValue(attr) as byte[];
Debug.WriteLine($"Nullable Attribute flags:
{string.Join(",", flags)}");
}
The output from that is:
Field NormalString - Type String
Nullable Attribute flags: 1
Field NullableString – Type String
Nullable Attribute flags: 2
To review, annotating a Reference Type variable with a ? tells the compiler to expect possible nulls for the purpose of warning about possible Null Reference Exceptions, but it does not change the underlying type.
Implementing Nullable Reference Types
Now that you know what Nullable Reference Types is supposed to do, you can enable the feature and start exploring its effects and implications.
Enabling and Disabling the Feature
The Nullable Reference Types feature is controlled by a project level setting in the .csproj file as shown here:
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
The default value for new projects is enable, so when you create a new Visual Studio project, the feature is already on. To turn it off, set it to disable, or remove the setting entirely, which is the state for legacy projects.
In addition to enable and disable, there are two other possible values for this setting: warnings, and annotations. On top of that, it's possible to control the setting at a file-by-file level using pragmas. These are more advanced techniques that exist to support updating legacy code bases. I'll touch on that in a bit, but for now, I'm going to focus exclusively on what happens when you enable Null Reference Type for an entire project.
The Main Warnings
Here are of the main warnings you're likely to encounter once you've enabled Nullable Reference Types. This list is not comprehensive but covers the most common warnings in my experience.
CS8602: Dereference of a possibly null reference. This occurs when you access a member of a variable that may be null.
string? s1 = null;
int i = s1.Length; // CS8601 warning
int j = s1?.Length; // safe
Polygon? p = null;
int i2 = p.Sides; // CS8601 warning
int j2 = p?.Sides; // safe
This is the main warning to look out for. If this code executes and attempts to dereference a null reference, you'll get a Null Reference Exception at runtime. Address this using a null check such as the null conditional operator or similar null check.
CS8600: Converting null literal or possible null value to non-nullable type. This warning occurs when you assign a nullable value or null to a non-nullable variable.
string? s1 = null;
string s2 = s1; // CS8600 warning
string s3 = s1 ?? string.Empty; // safe
Polygon? p1 = null;
Polygon p2 = p1; // CS8600 warning
Polygon p3 = p1 ?? new(); // safe
One way to think of this is as being similar to a narrowing conversion error. You can safely assign a non-nullable to a nullable. But if you do the reverse and assign a nullable to a non-nullable, you might end up with a non-nullable that's null. The simple way to address this is with the null coalescing operator to ensure a value for the non-nullable.
CS8603: Possible null reference return. This occurs when a method with a non-nullable return type may return null.
string GetName(int id)
{
// look up some record by id and return the name
// if no record is found...
return null; // CS8603 warning
return ""; // safe
}
Polygon GetShape(int sides)
{
// find a shape with that number of sides
// if no matching shape is found...
return null; // CS8603 warning
return new() { Sides = sides }; // safe
}
Address this by modifying the functions to ensure that they return a default value rather than null.
CS8604: Possible null reference argument. This occurs when you pass a possibly null value to a parameter that is not nullable.
void PrintStrLen(string s)
{
Debug.Print(s.Length.ToString());
}
string? maybeNull = null;
PrintStrLen(maybeNull); // CS8604 warning
void PrintPolySides(Polygon p)
{
Debug.Print(p.Sides.ToString());
}
Polygon? maybeNullObj = null;
PrintPolySides(maybeNullObj); // CS8604 warning
In this case, the functions, PrintStrLen and PrintPolygonSides expect a non-nulllable parameter but are given one that might be null. The best way to address this warning is to modify the functions so that they accept a nullable input and implement the appropriate null checks within their function bodies.
// CS8604 resolved by modifying the function
void PrintStrLen(string? s)
{
Debug.Print(s?.Length.ToString() ?? "");
}
string? maybeNull = null;
PrintStrLen(maybeNull); // safe
// CS8604 resolved by modifying the function
void PrintPolySides(Polygon? p)
{
Debug.Print(p?.Sides.ToString()" ?? "");
}
Polygon? maybeNullObj = null;
PrintPolySides(maybeNullObj); // safe
CS8618: Non-nullable field/property is uninitialized. This occurs when a non-nullable field or property is not initialized in the constructor.
class MyClass
{
public string Name {get; set;} // CS8618
public string Name2 {get; set;} = ""; // safe
public Polygon Poly; // CS8618
public Polygon Poly2 = new(); // safe
}
The way to resolve this is either to annotate the fields and properties to use nullable types or to provide a field or property initializer, as shown in the code snippet above. In C# 11.0 you also have an option to mark a field or property as required like this:
public required string Name;
With the field marked as required, the compiler will check any instantiation of the class to and warn if a null value is being set:
Person p1 = new() { Name = "Fred" }; // OK
Person p2 = new() { Name = "" }; // OK
Person p3 = new() { Name = null }; // CS8625
CS8625: Cannot convert null literal to non-nullable reference type. This occurs when you directly assign null to a non-nullable reference type.
void DoSomething(string s) { ... }
DoSomething(null); // CS8625 warning
void DoSomethingElse(Polygon p) { ... }
DoSomethingElse(null); // CS8625 warning
In this case, the variable in question should probably just be annotated as Nullable.
CS8629: Nullable value type may be null. This occurs when you use the Value property of a Nullable Value Type without first checking for a possible null.
int? i = null;
int j = x.Value; // CS8629 warning
Note that this one doesn't relate to Nullable Reference Types, but rather happens when you attempt to refer to the Value of a Nullable Value Type without first checking to see if it has a value. It's only enabled if you enable the Null Reference Types feature, so I suppose this is kind of a bonus warning.
The Null Forgiving Operator
There are situations where you, as a human programmer, know that a Nullable Reference Type cannot be null, but the static analysis can't figure it out. Here's an example:
Polygon? _poly;
void Program()
{
InitPoly();
UsePoly();
}
void InitPoly()
{
_poly = new() { Sides = 3 };
}
void UsePoly()
{
int x = _poly.Sides; //CS8602
}
In this case, you know that _poly is going to get initialized before the UsePoly() is called. Therefore, the dereferencing of _poly.Sides is safe. But still, you get the CS8602 warning. You can use the Null Forgiving Operator to suppress this warning, like this:
void UsePoly()
{
int x = _poly!.Sides; //! is Null Forgiving
}
The Null Forgiving Operator, an ! added after the variable, tells the compiler, “I know this one will never be null, suppress the warnings.”
Use the Null Forgiving Operator sparingly and as a last resort.
The Null Forgiving Operator sometimes generates a bit of controversy and conversation. Some folks will take the view that it's a hack that should never be used. The idea is that any and all reference types could possibly be null and that a responsible programmer will just annotate the variable as nullable and address any warnings that happen. Other folks consider the Null Forgiving Operator to be a blessing that allows them to constructively use Null Reference Types without rewriting their entire project. It's probably prudent to use the Null Forgiving Operator sparingly and as a last resort, but I have encountered real-world situations where it was the only logical solution.
Updating an Existing Project
Starting a new project with Nullable Reference Types enabled is straightforward. The main thing is to remember is to make conscious choices about your variable annotations as you go. Ask yourself, “Is this string or object reference nullable or not nullable based on the logic of my program?” Then, annotate or initialize accordingly and prevent compiler warnings up front.
Dealing with an existing codebase is a different matter. You might be faced with tens of thousands of lines of code that need to be reviewed, annotated, and possibly fixed. Your initial thought might be, “Why bother?” Why go to all the trouble to annotate and update a code base that is working just fine?
It turns out that there are legitimate reasons for wanting to update an existing codebase to use Nullable Reference Types. The most important one is that your code base is used by other code bases. Maybe you've written a class library or framework that's referenced by other projects within your organization. Or maybe your code is published as a NuGet package and referenced by projects outside of your organization. In these cases, you need to assume that consumers of your code may want to use Nullable Reference Types and therefore your library should be properly annotated.
So how does one go about updating an existing codebase to use Nullable Reference Type? There are several approaches you can take, and some are better than others, as you'll see. Note that this is where those two additional project-level options for the feature, annotations and warnings, come into play. It's also where we get to use the file-level pragmas in combination with the project settings to customize the compiler behavior at the file level.
Bombs Away!
One approach you might take to updating an existing codebase is simply to enable Nullable Reference Types at the project level like this:
<Nullable>enable</Nullable>
Then just see what happens. The challenge with this approach is that it will likely generate thousands of warnings right off the bat. Resolving these can cause new ones to appear. This can lead to a frustrating and chaotic game of whack-a-mole that doesn't always result in a well annotated and safe code base. I can only recommend this for the smallest and simplest of projects. There are better ways.
File-By-File
The idea with the File-By-File approach is to enable Nullable Reference Types for one file at a time. First, you disable Nullable Reference Types at the project level like this:
<Nullable>disable</Nullable>
Then, you add the following two file level pragmas:
#nullable enable annotations
#nullable enable warnings
You can control Nullable Reference Types at the file level using Pragmas, which are file-level compiler directives.
The first pragma turns on your ability to add annotations, that is, your ability to apply the ? after your variable declarations. If you don't turn annotations on in this way, the act of adding an annotation to a variable generates the following compiler warning: “CS8632: The annotation for nullable reference types should only be used in code within a ‘#nullable’ annotations context.”
The second pragma instructs the compiler to check the current file for possible issues and generate the various Nullable Reference Types warnings. With these two pragmas in place, you then proceed to update the annotations and clear any warnings for the current file. Once all the files are done, you can remove all the pragmas and enable the feature at the project level.
This is better than just enabling the feature globally, but it's still not ideal. When you simply enable Nullable Reference Types either globably or file-by-file, the compiler may actually skip the most important code. Why is that? This is because your legacy string and object references are now interpreted to mean should-never-be-null even though when you wrote the code originally, they meant could-possibly-be-null. In other words, the flavor of your reference types has switched and the compiler isn't going to give you warnings for anything it sees as should-never-be-null. To address this, I recommend taking an approach I call “Annotations First.”
Annotations First
With the Annotations First approach, the idea is to first go through your project and annotate it completely before thinking about what warnings might be generated.
To turn annotations on globally, set the Nullable option to annotations at the project level:
<Nullable>annotations</Nullable>
This is the same as setting the #nullable enable annotations pragma for every file in the project.
Next, go through your entire project, analyze every variable, and decide if it should be nullable or not. For example, let's say you have this function in your library:
public static string DoubleIt(string s)
{
if (string.IsNullOrEmpty(s))
{
return string.Empty;
}
else
{
return $"{s}{s}";
}
}
As written, this function can safely accept any string as an input, including null. However, it never returns a null string. To preserve this meaning, annotate it as follows:
public static string DoubleIt(string? s)
{
// etc...
}
This input variable, s, is nullable but the return value is not.
Here is another example. Imagine you have this class in your library:
public class Person
{
public string Name;
public Person Parent;
}
In practice, you know that the Name field should never be null, but that Parent is optional. Accordingly, you annotate your class like this and provide the required initializer for Name (or mark it as required):
public class Person
{
public string Name = "";
public Person? Parent;
}
Once you have annotated all of your files, go file by file setting the #nullable enable warnings pragma, make the required code corrections (or occasionally add the Null Forgiving Operator) to clear the warnings. When you are all done, you can remove all the file level pragmas and enable the feature globally.
The key idea is that you focus first on the true meaning and semantics of your code rather than letting the compiler warnings, or possible lack-thereof, guide your process. This ensures that your annotations are faithful to the original intent of your code. Your code gets safer without subtlety changing its meaning.
Annotating first allows you to focus on the true meaning and semantics of your code and ensures that your annotations are faithful to the original intent and not just driven by the need to suppress compiler warnings.
Critique
Many programmers don't like Nullable Reference Types. In fact, many of us simply turn it off. I'll admit to having done this myself on more than one occasion. So, what are the supposed problems with this feature? Why does it generate such resistance?
They Are Still Nullable.
Non-nullable reference types like string or Polygon aren't truly non-nullable. You can still set them to null and your code will compile just fine. Compare this to the behavior of non-nullable Value Types:
int i = null; // will NOT compile
bool b = null; // will NOT compile
string s = null; // compiles (with warning)
Polygon p = null; // compiles (with warning)
In other words, with Nullable Reference Types enabled, types like string and Polygon really are more like “compiler-assisted probably-not-null” rather than “non-nullable.” This is confusing and creates a subtle ambiguity, as well as a false sense of security.
It's Backwards
C# reference types have always been nullable. Since version 1.0, it was understood that objects and strings might be null. So how then, can one suddenly enable Nullable Reference Types in C# 8.0? They've existed all along. Wouldn't it be better to name the feature “Non-Nullable Reference Types,” or, more accurately, “Most Likely Not Null Reference Types”? From that perspective, wouldn't it be better to annotate the non-nullable ones rather than the nullable ones? Imagine that instead of using the ? annotation to mark a nullable, we used a * annotation to mark the non-nullable ones. The type string would still mean can-be-null, but the type string* would mean, should-never-be-null. Legacy code would still mean the same thing, but the new idea, the concept of non-nullability, would be called out. I think this would be so much easier to explain and understand.
Adds “Hair” to C#
One of the things that has always made C# elegant and easy to use is that it doesn't contain a lot of complicated little symbols doing subtle things. Compare to C++ where this code: int* x = &p; and this code: int x = *p; are both valid and mean totally different things. These variable and type decorators in C++ (and other languages) are sometimes sarcastically referred to as “hair.” They sprout out from variables and types and change their meaning and behavior. C# has, for the most part, avoided this kind of thing, but with Nullable Reference Types, it starts to grow some unwanted hair.
It Doesn't Lead to Better Code
Most programmers learn early to write defensive code and avoid Null Reference Exceptions. Does the Null Reference Types now give a false sense of security that you no longer need to think about nullability because the compiler is going to warn you? Will this, over time, make programmers sloppier?
Likewise, most programmers learn early to pay attention to compiler warnings because ignoring or thoughtlessly suppressing warnings can lead to some serious code problems. Does Null Reference Types suddenly generate so many warnings that programmers start to ignore them or seek the quickest way to suppress them? Overall, then, does this feature really encourage coding best practices? In the long run, will code be better and safer?
It Solves a Problem that Doesn't Exist
In theory, Null Reference Exceptions are a serious problem. In practice, many very large code bases rarely experience them. I recently built a C# AKS-hosted microservices solution that was deployed and has been running continuously for over a year without encountering a single Null Reference Exception. In this project, Nullable Reference Types is disabled. Does this large and complex feature solve a real problem? Is your code really at risk?
It's a Half-Measure
Newer languages like Swift (2014), Rust (2015), and Kotlin (2016) have truly non-nullable reference types. In Swift, for example, String and String? are different types and only String? can be nil. However, the reality is that when C# was first launched in 2002, this was not the norm. Then, high-productivity languages like Delphi, Java, and Visual Basic assumed that object references could be null. They made them safer and easier to use than C++ pointers. But they still preserved the idea of a null pointer. That was the norm.
So now, is C# wishing to re-write this history? If so, the current implementation of Nullable Reference Types is a half-measure. As noted above, these types are all actually nullable regardless of their annotations. This seems like a compromise that doesn't actually provide the clarity and true safety found in Rust, Swift, and Kotlin.
Overloads the ? Symbol (Yet Again)
It's a good thing when an operator, symbol, or keyword has just one meaning. In C#, = means “assign”, == means “test for equality,” + means “add”, and ++ means “increment”. The => operator means “goes to”. The poor ? and its variants, however, has now been overloaded with seven different meanings:
// 1. Ternary conditional operator (C# 1.0)
string s = thing == null ? "No name" : thing.Name;
// 2. Nullable value type (C# 2.0)
int? x = null;
// 3. Null-coalescing operator (C# 2.0)
string s = thing.Name ?? "No name";
// 4. Null-conditional operator (C# 6.0)
string s = thing?.Name;
// 5. Nullable reference type annotation (C# 8.0)
string? s = names.Find(x => x.Length == 2);
// 6. Null-coalescing assignment (C# 8.0)
string s ??= "Default";
// 7. Null-conditional assignment (C# 14.0)
myPolygon?.Sides = 3;
Nullable Reference Types is obviously not 100% to blame for overloading ?, but it certainly doesn't make the situation any better.
Nobody Understands It
Earlier in this article, I spent over 3500 words explaining how Null Reference Types works. Can you imagine if it took 3500 words to explain a feature like value tuples or lambda expressions? Those are also complex and powerful features with some subtleties. But 3500 words? This is probably the most devastating critique of Null Reference Types: It attempts to make the language simpler and safer, but instead it introduces ambiguity, complexity, and resulting confusion. How can it make code better and safer if many people don't really understand it?
Counter-Criticism: You're Just a Curmudgeon
I'll admit it. I'm old-fashioned. I wash my car by hand and dry it with a chamois. I cook in a cast-iron skillet, don't do coffee “flights” or put truffle oil on or in anything. I like to hand write ANSI SQL 1992-compliant SQL statements and you won't ever hear me use the phrases “slaps” or “bussin.” So, when it comes to Nullable Reference Types, is my resistance just a case of being a stick-in-the mud?
I don't think so. When it comes to many things, and C# in particular, I'm an early adopter who eagerly embraces change. I actively work to integrate the latest C# features, like target-typed constructors and pattern-matching switch statements into my code. I'll admit that, when I first saw the type declaration string?, my initial reaction was, “What on earth is this?” But I don't think my skepticism of Null Reference Types is rooted in resistance to change. Now that I've learned what these annotations do, my initial knee-jerk response has matured into a more fact-based skepticism.
Conclusion
Every C# programmer is now faced with a choice: For any given project, are you going to enable Nullable Reference Types or not? If you're working on shared code libraries and frameworks and NuGet packages that are consumed outside of your organization, you probably have no choice. In order to be a good citizen of the C# ecosystem, you need to annotate your libraries and make them play nice with client code that has Nullable Reference Types enabled. If, however, your code is fully self-contained, I think you can legitimately disable this feature without feeling too bad about it, especially if you have strong coding practices in place, like adherence to a style guide, code reviews, automated unit testing, etc. I'm living proof that it's possible to write perfectly reliable, safe C# code without the Nullable Reference Types compiler warnings. If you do choose to use it, I suggest taking some time with your team to make sure they all really understand what's going on. You can even refer them to this article!



