C# null safety is a contract between the code and the compiler. Annotations say what may be missing, the analysis follows each value through every branch, and the operators keep the checks short. These are the decisions that keep that contract honest.
Turn nullable reference types on and keep them on
The <Nullable>enable</Nullable> project setting makes every reference type non-nullable unless it carries a ?, and starts the compiler tracking null state through the whole project. New projects get the setting from the template. A project that predates the feature can adopt it file by file with #nullable enable, working from the deepest types outward, then drop the directives once each file has migrated.
Preferred
#nullable enable
string? middle = ReadMiddleName();Avoid
string? middle = ReadMiddleName();Corpus: Nullable reference types · Nullable migration strategies
Put the nullability in the signature
A reference type without ? is a promise that the value is never null; string? says the value may be missing. Writing the annotation down puts the question in the signature, where the compiler checks every caller and every dereference against it, instead of leaving each reader to guess.
Preferred
string? nickname = ReadNickname();
if (nickname is not null)
{
Console.WriteLine(nickname.Length);
}Avoid
string nickname = ReadNickname();
Console.WriteLine(nickname.Length);Corpus: Tutorial: Express your design intent with nullable and non-nullable reference types · Nullable reference types
Make required members part of the contract
A non-nullable member that no constructor assigns makes the compiler report CS8618. The required modifier moves that duty to the caller: the object initializer must set the member, or the code does not compile. An = null! initializer does the opposite, silencing the warning while leaving a half-built object behind.
Preferred
var order = new Order { Customer = "Ada" };Avoid
var order = new Order();Corpus: required modifier · Resolve nullable warnings
Use the null-forgiving operator sparingly
The ! operator changes the null state the compiler tracks for one expression and does nothing at run time. Where a check or an annotation can carry the guarantee, ! is a warning switched off. It is earned only when the guarantee lives outside the code the compiler can see: a test that passes null on purpose, or an API whose contract the compiler cannot read.
Preferred
Assert.Throws<ArgumentNullException>(() => Greet(null!));Avoid
var name = FindName()!;
Console.WriteLine(name.Length);Corpus: ! (null-forgiving) operator · Resolve nullable warnings
Test for null with is null and is not null
A null test is how the analysis learns a value is safe to use: inside the if (value is not null) branch every dereference of value is allowed, and the state carries on into the code that follows a guard clause. A type can overload ==, so value != null is a method call whose answer the type controls, while the patterns is null and is not null always test the actual null reference.
Preferred
if (value is not null)
{
Use(value);
}Avoid
if (value != null)
{
Use(value);
}Corpus: Null operators in C# · Patterns
Give a fallback with ?? and fill lazily with ??=
?? evaluates its right operand only when the left one is null, so the fallback work is skipped when a value already exists, and a ?? b ?? c returns the first value that is present. ??= is the assignment form: the field is built on first use and left alone afterwards.
Preferred
_index ??= BuildIndex();
return _index;Avoid
if (_index is null)
{
_index = BuildIndex();
}
return _index;Corpus: ?? and ??= operators - null-coalescing operators · Null operators in C#
Traverse nullable chains with ?. and ?[]
The null-conditional operators evaluate the receiver once, skip the access when it is null, and short-circuit the rest of the chain, so order?.Customer?.Name is safe where order.Customer.Name throws. The same property makes Changed?.Invoke(...) the safe way to raise an event. A value member read this way comes back as its nullable type, so apply ?? when a plain value is needed.
Preferred
string city = order?.Customer?.Address?.City ?? "unknown";
Changed?.Invoke(this, args);Avoid
string city = order.Customer.Address.City;Corpus: Member access operators and expressions · Null operators in C#
Ask with TryGetValue and TryParse, do not catchCA1854
The Try pattern reports failure through a return value and hands the value over in the same call, so a case the caller expects does not throw. TryGetValue also avoids the double lookup of ContainsKey followed by the indexer, and TryParse returns false for input that Parse would reject with an exception.
Preferred
if (_scores.TryGetValue(name, out int score))
{
return score;
}
return 0;Avoid
if (_scores.ContainsKey(name))
{
return _scores[name];
}Corpus: CA1854: Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method · Common collection types in C# · How to convert a string to a number
Reject a null argument at the top of the methodCA1510
A public entry point checks what it cannot work without before doing anything else. ArgumentNullException.ThrowIfNull is the compact form of the guard. Give it a value that can actually be null: a non-nullable struct, a nameof expression, or a new expression never throws there, so the call is dead code and boxes the struct.
Preferred
ArgumentNullException.ThrowIfNull(message);Avoid
if (message is null)
{
throw new ArgumentNullException(nameof(message));
}Corpus: CA1510: Use ArgumentNullException throw helper · CA2264: Do not pass a non-nullable value to 'ArgumentNullException.ThrowIfNull'
A null string and an empty string both mean no textCA1820
An empty string is a real object that answers method calls; null is not, and any member access on it throws. To a caller both mean nothing was supplied. String.IsNullOrEmpty tests the pair in one call, where a comparison against "" misses the null case. When white space is as meaningless as absence, as in a name, String.IsNullOrWhiteSpace rejects that too instead of storing blank input.
Preferred
if (string.IsNullOrWhiteSpace(userName))
{
throw new ArgumentException("Name is blank", nameof(userName));
}Avoid
if (userName == "")
{
throw new ArgumentException("Name is blank", nameof(userName));
}Corpus: Strings · CA1820: Test for empty strings using string length · Automatically implemented properties
Record what a helper method initializes
Null analysis reads a method body on its own and does not follow calls, so a constructor that delegates to an initializer helper still reports CS8618 for the fields the helper assigns. [MemberNotNull(nameof(_window))] states the postcondition on the helper: after it returns, the named members are not null. The warning goes away without changing the constructor.
Preferred
[MemberNotNull(nameof(_window))]
void Initialize()
{
_window = OpenWindow();
}Avoid
void Initialize()
{
_window = OpenWindow();
}Corpus: Attributes interpreted by the compiler: Nullable static analysis · Resolve nullable warnings
Annotate Try methods and custom null checks
The compiler does not see inside a method it calls, so it treats an out parameter as maybe-null whatever the body proves, and it does not recognize a helper that checks for null. [NotNullWhen(true)] supplies that missing fact: when the method returns true, the argument or out value is not null. The runtime annotates TryGetValue and IsNullOrEmpty this way, and the attribute works on a custom test of your own.
Preferred
bool TryGet(string key, [NotNullWhen(true)] out string? value)Avoid
static bool TryGet(string key, out string value)
{
value = null;
return false;
}Corpus: Attributes interpreted by the compiler: Nullable static analysis · ! (null-forgiving) operator
Do not read T? as always nullable
For an unconstrained type parameter, T? means the nullable reference form only when the caller supplies a reference type; Box<int> still gets int. The constraints settle the meaning: where T : class requires a non-nullable reference type and class? allows a nullable one. A generic method that returns default for a missing result should say so with [return: MaybeNull] instead of letting callers assume the value is present.
Preferred
[return: MaybeNull]
T Find<T>(string key) => default;Avoid
T Find<T>(string key) => default;Corpus: Nullable reference types · Attributes interpreted by the compiler: Nullable static analysis
Extract a nullable value type with a pattern
T? over a value type is a value plus a flag: the default is null, not zero, and an operator applied to a null operand lifts the whole result to null. The type pattern is int degrees checks and extracts in one step. Value throws when the flag is false, so keep it behind HasValue or replace it with GetValueOrDefault or ??.
Preferred
if (temperature is int degrees)
{
Console.WriteLine(degrees * 2);
}Avoid
Console.WriteLine(temperature.Value * 2);