Codectionary / Developer documentation / C#

Nullable Reference Types & Null-Coalescing

Since C# 8, nullable reference types let the compiler track and warn about potential null reference issues at compile time, even for reference types like string and custom classes (which were always implicitly nullable before). A type like string means 'never null' under this feature, while string? explicitly allows null. The null-coalescing operator (??) and null-conditional operator (?.) provide concise syntax for safely working with potentially-null values.

Syntax

string? maybeNull = null;\nvar result = value ?? defaultValue;

Examples

Nullable vs Non-Nullable Reference Types

With nullable reference types enabled, the compiler warns if a non-nullable reference might be null.

#nullable enable

string name = "Fola";       // never null (compiler-enforced, with warnings on violation)
string? nickname = null;     // explicitly allowed to be null

Console.WriteLine(name.Length);       // safe, no warning
// Console.WriteLine(nickname.Length);  // compiler WARNING - could be null
if (nickname != null)
{
    Console.WriteLine(nickname.Length);  // safe now, compiler knows it's checked
}

Null-Coalescing Operator (??)

Providing a fallback value when something might be null, in a single concise expression.

string? input = null;
string result = input ?? "default value";
Console.WriteLine(result);  // default value

int? maybeAge = null;
int age = maybeAge ?? 0;
Console.WriteLine(age);  // 0

Null-Conditional Operator (?.)

Safely accessing a member only if the object isn't null, short-circuiting to null instead of throwing.

class Address { public string City = ""; }
class Person { public Address? Home; }

Person p = new Person();  // p.Home is null

string? city = p.Home?.City;  // null, instead of a NullReferenceException
Console.WriteLine(city ?? "No address on file");

int? length = p.Home?.City?.Length;  // chains safely through multiple levels

Null-Coalescing Assignment (??=)

Assigning a value only if the variable is currently null.

List<string>? names = null;

names ??= new List<string>();  // only assigns if names is currently null
names.Add("Fola");

Console.WriteLine(string.Join(", ", names));

Best practices

  • Enable nullable reference types (<Nullable>enable</Nullable> in the project file) for new projects - it catches a huge class of NullReferenceException bugs at compile time
  • Use ?. and ?? together (obj?.Property ?? defaultValue) for concise, safe access with a fallback in a single expression
  • Use ??= to lazily initialize a variable only if it is currently null, instead of a longer explicit if check
  • Treat compiler nullable warnings seriously rather than suppressing them with ! (the null-forgiving operator) unless you are certain a value cannot actually be null at that point

At a glance

Purpose
Applications on the .NET platform
File extension
.cs
Runs in
.NET runtime
Usually used with
.NET SDK and libraries

Specifications & further reading

Related C# documentation

Delegates
A delegate is a type-safe reference to a method - essentially a variable that can hold a method and be invoked like one. Delegates enable passing behavior around as data, forming the foundation for events, callbacks, and LINQ. C# provides built-in generic delegate types, Func<> and Action<>, that cover the vast majority of use cases without needing to declare a custom delegate type.
Events
An event is a special kind of delegate field that provides a publish-subscribe pattern: the declaring class can raise (invoke) the event, but external code can only subscribe (+=) or unsubscribe (-=) - it cannot invoke the event directly or overwrite existing subscribers. This makes events the standard, safe way to notify other parts of a program that something has happened, like a button being clicked or a value changing.
Extension Methods
Extension methods let you add new methods to an existing type - including types you don't own, like built-in .NET types or third-party classes - without modifying its source code or creating a subclass. They're defined as static methods in a static class, with the first parameter prefixed with this to indicate which type they extend. LINQ's Where(), Select(), and friends are themselves implemented as extension methods on IEnumerable<T>.
Pattern Matching
Pattern matching, expanded significantly since C# 7, lets you test a value against a shape or condition using is, switch expressions, and more, often extracting matched data in the same step. It covers type patterns (checking and casting in one step), property patterns (matching on an object's properties), and relational patterns (matching numeric ranges), making conditional logic more expressive and less error-prone than manual casting.