C# tutorials > Modern C# Features > C# 6.0 and Later > What are non-nullable reference types (NRTs) and how do they improve null safety?
What are non-nullable reference types (NRTs) and how do they improve null safety?
Basic Introduction to NRTs
string
, for instance, cannot be assigned the value null
without explicit indication that it can be nullable. To allow a reference type to hold null
, you must use the ?
suffix, making it a nullable reference type (e.g., string?
). The compiler will issue warnings if you attempt to assign null
to a non-nullable reference or dereference a nullable reference without checking for null
first.
// Non-nullable string (cannot be null)
string name = "John";
// Nullable string (can be null)
string? nullableName = null;
// Attempting to assign null to a non-nullable string will produce a warning
// name = null; // Compiler warning: CS8600 Converting null literal or possible null value to non-nullable type.
Enabling NRTs in Your Project
<Nullable>enable</Nullable>
property to your project's .csproj file. This tells the compiler to treat reference types as non-nullable by default and to perform nullability analysis. You can also set it to 'disable', 'warnings' or 'annotations' for different levels of strictness. ImplicitUsings allow for global using statements to be declared.
<!-- .csproj file -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
Dealing with Nullable References
null
before dereferencing them. The compiler will warn you if you attempt to use a nullable reference without a null check. The above code demonstrates a simple null check using an if
statement. You can also use the null-conditional operator (?.
) or the null-coalescing operator (??
) for more concise null handling.
string? nullableName = GetNameFromDatabase();
if (nullableName != null)
{
Console.WriteLine("Hello, " + nullableName.ToUpper());
}
else
{
Console.WriteLine("Name not found.");
}
Using Null-Conditional and Null-Coalescing Operators
?.
) allows you to access a member of a nullable reference only if the reference is not null
. If the reference is null
, the expression evaluates to null
. The null-coalescing operator (??
) provides a default value if the left-hand operand is null
. This makes your code more concise and readable while still handling null values safely.
string? nullableName = GetNameFromDatabase();
// Null-conditional operator
string? upperCaseName = nullableName?.ToUpper();
// Null-coalescing operator
string displayName = nullableName ?? "Guest";
Console.WriteLine($"Upper Case Name: {upperCaseName ?? "(Null)"}");
Console.WriteLine($"Display Name: {displayName}");
Attributes for Fine-Grained Control
[AllowNull]
, [DisallowNull]
, [NotNull]
, and [MaybeNull]
to provide more precise control over nullability. These attributes can be used to annotate parameters, return values, and properties to indicate whether they can be null. They help the compiler perform more accurate nullability analysis and provide more informative warnings.
public class MyClass
{
// Indicates that the method will always return a non-null value
[return: NotNull]
public string GetName() => "Example";
// Indicates that the parameter cannot be null
public void ProcessName([NotNull] string name)
{
Console.WriteLine(name.ToUpper());
}
//Indicates that the parameter can be null
public void ProcessNullableName([AllowNull] string? name)
{
}
}
Concepts Behind the Snippet
null
as an exceptional case rather than the default. This forces developers to be more explicit about when a reference can be null
, leading to more robust and less error-prone code. By enabling NRTs, the compiler can help you catch potential null reference exceptions at compile time, preventing runtime errors. The use of annotations like ?
and attributes helps to provide even more fine-grained control and context to the compiler.
Real-Life Use Case Section
string
without knowing if it's potentially null
. If you then try to use this name without checking for null
, you might get a NullReferenceException
. With NRTs, the database access method would return a string?
, clearly indicating that the name could be null
. This forces you to handle the potential null
case, such as displaying a default name or logging an error. This is crucial in preventing unexpected application crashes. NRTs are valuable when working with external data sources where nullability is uncertain.
Best Practices
string?
) only when a value is truly optional or could be null
.null
when working with nullable reference types using if
statements, the null-conditional operator (?.
), or the null-coalescing operator (??
).[AllowNull]
, [DisallowNull]
, [NotNull]
, and [MaybeNull]
to provide more precise nullability information.
Interview Tip
When to Use Them
Memory Footprint
string?
) does introduce a slight overhead, as the compiler needs to track whether the reference is null
or not. This overhead is typically negligible, especially compared to the benefits of improved null safety. Consider that handling the possibility of nulls with checks and exception management may have a higher overhead than the small cost of nullable tracking.
Alternatives
null
before dereferencing a reference. While this approach can be effective, it can also be tedious and error-prone. Other alternatives include using code analysis tools to identify potential null reference issues. However, NRTs provide a more comprehensive and automated approach to null safety, making them the preferred choice in modern C# development. There are also design by contract tools or approaches using validation libraries.
Pros
null
and which cannot.
Cons
FAQ
-
What happens if I don't enable NRTs in my project?
If you don't enable NRTs, reference types will behave as they always have in previous versions of C#. They will be implicitly nullable, and the compiler will not perform nullability analysis. You will not receive any warnings about potential null reference exceptions. -
Can I disable NRTs for specific files or regions of code?
Yes, you can disable NRTs for specific files or regions of code using preprocessor directives. For example, you can use#nullable disable
to disable NRTs for a specific file. However, it's generally recommended to enable NRTs for your entire project to get the most benefit from them. -
How do NRTs interact with legacy code?
When working with legacy code that doesn't use NRTs, the compiler will treat reference types as implicitly nullable. This means that you might need to add null checks or use attributes to suppress warnings. It's generally a good idea to gradually migrate legacy code to use NRTs to improve its null safety. -
Are NRTs a replacement for unit tests?
No, NRTs are not a replacement for unit tests. They are a complementary tool that can help you catch potential null reference exceptions at compile time. However, unit tests are still essential for verifying the correctness of your code and ensuring that it behaves as expected.