Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
FREEDOM251

Freedom251 › Blog › C++

C# nullable reference types: Why runtime nulls can appear

By Freedom251 Team · · 8 min read

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

C# nullable reference types reduce null-related bugs by warning during compilation, but they do not make references non-null at runtime. A variable declared as string is a promise enforced by analysis, not a runtime barrier. Null can still arrive through the null-forgiving operator, default-initialized structs, fresh arrays, inaccurate API contracts, and runtime mechanisms such as reflection or deserialization. Treat warnings as evidence about possible states, then initialize and validate data at every boundary where the compiler cannot prove the facts.

What nullable reference types actually guarantee

Nullable reference types (NRTs) add annotations such as string and string? plus flow analysis. The compiler tracks whether an expression is believed to be null or not null and emits diagnostics when your code appears to dereference a possibly null value. Microsoft describes this as a compile-time feature: annotations and null-state analysis guide warnings, while runtime behavior remains unchanged.

As an Amazon Associate I earn from qualifying purchases.

The CLR still represents a reference as either an object reference or null. The annotation does not allocate an object, insert a check, or alter metadata in a way that enforces the contract at every call. A non-nullable declaration therefore expresses intended use: callers should receive a value, and implementations should return one. If another part of the program violates that intention, the runtime accepts the value.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

This is different from a nullable value type such as int?. int? is a Nullable<int> value with a defined representation and operations such as HasValue. NRT syntax applies to reference types; it changes compiler analysis, not the representation of string, arrays, classes, delegates, or interfaces.

#1 Best Overall
Elebase USB to USB C Adapter for iPhone 18 Pro Max,USBC Car Charger Adapter | 4Pack Type C Female to A Male Cable Converter for Apple Duo 17 16 15 14 Plus,iWatch Watch 12 11 10,iPad 9 8 Air,Airpods 5 4
  • Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
  • Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
  • Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
  • Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
  • 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.

At a glance: the five escape routes

Escape route Where null appears Why analysis may not stop it Most effective defense
Null-forgiving operator (!) A source expression The operator explicitly tells the compiler to treat the expression as non-null and emits no runtime check. Remove the assertion, prove the condition, or validate before dereferencing.
Default-initialized struct Fields or properties of a value type default and parameterless construction supply default field values, including null references. Use an initializing constructor or required members and avoid relying on an uninitialized default.
New reference array Array elements Allocating the array creates slots; it does not create an object for each reference slot. Assign every element or use an initializer that supplies values.
Oblivious or inaccurate API contract A call or interop boundary Code without nullable context and code whose annotations do not match its implementation can pass null without a useful warning. Annotate producing APIs accurately and check values at boundaries you do not control.
Runtime mechanisms outside flow analysis Objects populated or changed by runtime facilities Reflection, serializers, and framework code can create states the compiler never proved safe. Validate external or framework-populated data before use.

1. The null-forgiving operator can assert something untrue

The postfix ! is called the null-forgiving operator. It changes the compiler’s null-state assumption for one expression. It does not change the value, test it, or throw an exception when the value is null.

string? configuredName = ReadSetting();
string name = configuredName!; // assertion only
Console.WriteLine(name.Length);

If ReadSetting() returns null, the assignment still stores null in the reference. The later Length access can throw NullReferenceException. The operator is useful when you have a proof the compiler cannot see, such as a framework callback that runs only after initialization, but it is dangerous when used to silence a warning without establishing that proof.

How to replace an unjustified assertion

Prefer a branch that establishes a fact:

string? configuredName = ReadSetting();
if (configuredName is null)
{
    throw new InvalidOperationException("A name is required.");
}
Console.WriteLine(configuredName.Length);

Or provide a deliberate fallback when absence is valid:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
string name = ReadSetting() ?? "Guest";
Console.WriteLine(name.Length);

These versions either stop at the boundary with a meaningful error or convert null into a documented value. Microsoft advises using ! sparingly because each occurrence marks a place where the compiler can no longer protect you. Keep an assertion only when the invariant is enforced elsewhere and document that enforcement close to the assertion.

Rank #2
Sale
Anker USB-C Hub, 5-in-1 USB Hub for Laptops, 4K HDMI Multiport Adapter | USB C Dongle with 90W Max Power Delivery, 3×5Gbps USB A Data Ports, for MacBook, Dell, and More (Charger Not Included)
  • 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
  • 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
  • Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
  • 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
  • What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.

2. A default struct can contain null non-nullable fields

Structs are value types, but their fields can be references. Every field receives its default value when the struct is created with default or parameterless new(). The default value of a reference field is null, even if the field is declared non-nullable.

public struct UserProfile
{
    public string DisplayName;
}

UserProfile profile = default;
// DisplayName is null at runtime.
Console.WriteLine(profile.DisplayName.Length);

The construction itself does not produce the warning you might expect. Null-state analysis treats the non-nullable field declaration as the intended contract, while the default value operation supplies the type’s bitwise/default state. A dereference can therefore fail even though the source says string, not string?.

Initialize the invariant explicitly

An initializing constructor makes the required value explicit:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
public struct UserProfile
{
    public string DisplayName { get; }

    public UserProfile(string displayName)
    {
        DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
    }
}

var profile = new UserProfile("Ada");

Required members can also force ordinary object-creation sites to provide a value. They do not turn the runtime into a validator, so code that can still obtain a default struct must be treated carefully. If a type cannot sensibly exist with default field values, a class with a validating constructor may be a better model than a struct.

Rank #3
Sale
Anker USB C Hub, 7in1 Multi-Port USB Adapter, 4K@60Hz USBC to HDMI Splitter | 85W Max PD, 2 USB 3.0 & 1 USBC Data Ports, SD/TF Card Reader, for Laptops/Mac and Other Type C Devices (Charger Not Included)
  • Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
  • Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
  • Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
  • Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
  • What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.

3. A newly allocated reference array starts full of nulls

new string[3] reserves three element slots and initializes each slot to the default value for string: null. It does not call a string constructor three times, and the element type does not promise that every slot has already been populated.

string[] names = new string[3];

// All three entries are null here.
Console.WriteLine(names[0].Length);

The declaration communicates what a valid element should be after initialization. It does not describe the state immediately after allocation. This distinction also applies to arrays of custom classes, interfaces, delegates, and other reference types.

Fill the slots before exposing or reading them

Assign each position:

string[] names = new string[3];
names[0] = "Ada";
names[1] = "Grace";
names[2] = "Linus";

Or construct the array with an initializer or collection expression that supplies every value:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
string[] names = { "Ada", "Grace", "Linus" };

When an array is intentionally sparse, declare that fact with a nullable element type, string?[], and check each element before use. That annotation makes absence visible to callers instead of hiding it behind a non-nullable element declaration.

Rank #4
Sale
UGREEN USB to USB C Adapter Combo 4-Pack, 10Gbps USB C Converter Space Gray | Data transfer & Fast charging for MacBook, iPhone 17/16/15, iPad, Samsung Galaxy, Apple Watch
  • Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
  • Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
  • Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
  • Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
  • Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft

4. Oblivious and inaccurate API contracts let null cross a boundary

Nullable analysis is only as reliable as the information available at the call site. Older libraries and projects compiled without a nullable context are “oblivious”: their signatures do not tell the compiler whether a reference may be null. Interop layers and generated code can have the same effect. A call into such code may return null without a warning because the caller has no trustworthy null-state annotation to analyze.

// Compiled in a nullable-oblivious component:
public static string LoadTitle() => null;

// In a nullable-enabled caller:
string title = LegacyComponent.LoadTitle();
Console.WriteLine(title.Length);

An enabled caller can also be misled by an annotated API whose implementation violates its own declaration:

public sealed class Catalog
{
    private string? _title;

    // The signature promises a value, but the implementation can return null.
    public string Title => _title!;
}

The compiler checks the declaration and the implementation it can see; it cannot enforce that every binary, native component, database adapter, or future implementation obeys a non-nullable promise. A dishonest contract is still a contract in metadata, so downstream code may proceed without warnings.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Repair the producing side, then guard the boundary

  • Enable nullable context in code you own and annotate return values, parameters, fields, and properties according to actual behavior.
  • Change a return type to string? when absence is legitimate, or make the implementation guarantee a value before returning.
  • At an untrusted boundary, validate immediately and fail with a useful exception or map the data into a validated domain type.
public static string LoadTitle(IDataSource source)
{
    return source.ReadTitle()
        ?? throw new InvalidDataException("The data source returned no title.");
}

Once a boundary check succeeds, the rest of the method can operate on the validated non-null value without suppressions.

Best Value
Sale
Anker USB C Hub, 5-in-1 USBC to HDMI Splitter with 4K Display | 85W Max PD, 1 Powered USB-C 5Gbps & 2 Powered USB-A 3.0 5Gbps Data Ports, for MacBook Pro, MacBook Air, Dell and More (Black)
  • 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
  • Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
  • Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
  • HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
  • What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

5. Runtime mechanisms can create states flow analysis never saw

Flow analysis follows the source code and the contracts visible to the compiler. Runtime facilities can bypass that path. Reflection can read or assign fields and properties dynamically. Serializers and web frameworks can instantiate objects and populate members from external data. Dependency-injection, proxy, and interop layers can likewise supply values after compilation. NRT annotations remain metadata and guidance; they do not add runtime checks to these operations.

public sealed class Options
{
    public string Endpoint { get; set; } = "https://example.invalid";
}

var options = new Options();
var property = typeof(Options).GetProperty(nameof(Options.Endpoint));
property!.SetValue(options, null); // runtime code can violate the annotation

Console.WriteLine(options.Endpoint.Length);

The reflection call is able to assign null because the runtime does not enforce the string annotation. The same principle applies when a serializer receives a missing or explicit null field and writes it into an object. Whether a particular framework rejects that input is framework- and configuration-dependent; the annotation alone is not the rejection mechanism.

Validate at deserialization and reflection boundaries

Treat data entering through a runtime facility as untrusted until it has been checked. Validate required strings, collection elements, and nested objects immediately after materialization. A simple guard keeps invalid state from spreading:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
static void Validate(Options options)
{
    if (string.IsNullOrWhiteSpace(options.Endpoint))
    {
        throw new InvalidDataException("Endpoint is required.");
    }
}

For larger payloads, map the populated transport object to a domain type whose constructor enforces invariants. This concentrates checks at the trust boundary instead of scattering null-forgiving operators throughout business logic.

Why warnings are still valuable

The existence of escape routes does not make nullable analysis pointless. Warnings identify paths where the compiler can prove, or reasonably suspect, that a null value may be used. They catch missing checks, incomplete initialization, and mismatched contracts before execution. What they cannot do is prove that every runtime actor obeys every annotation.

Use a warning as a request to establish an invariant, not as an invitation to append !. When the warning is false because a framework guarantees initialization, encode that guarantee in a narrow adapter or a documented assertion. When the warning reveals a real possibility, change the type, initialize the value, or handle the null case.

A practical workflow when a non-nullable value is null

  1. Find the first boundary. Identify where the reference entered the method: a return value, field, array element, struct instance, deserializer, reflection call, or interop API.
  2. Inspect construction. Look for default, parameterless struct creation, new T[length], and object population that occurs after the constructor.
  3. Search for assertions. Review every ! on the path. Replace it temporarily with an explicit check to discover whether the assumption is actually true.
  4. Check the contract. Confirm that the producing project uses nullable context and that its annotations match implementation behavior. Do not infer safety from a non-nullable signature alone when the component is external.
  5. Validate once at the edge. Reject or normalize invalid data as soon as it crosses into code you control, then pass a validated value deeper into the application.
  6. Add a regression test. Exercise the default, empty, missing, and explicit-null inputs that exposed the bug so the boundary remains protected.

The rule to remember

string means “this reference is intended to be non-null,” not “the runtime guarantees a string object.” Null can survive an explicit assertion, a default struct, an unfilled array slot, an unreliable API declaration, or a runtime population mechanism. Reliable code combines nullable annotations with explicit initialization, truthful contracts, and validation wherever data enters through a path the compiler cannot fully analyze.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

C++dotnetnull safetynullable reference typesSoftware Development

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.