1. Introduction
Imagine your code is a play, and variables are the actors. Every time a variable enters a new scene (code block), it can be "alive" (has a value) or a "ghost" (null). But here's the catch: the audience—the compiler—watches the play not live, but by reading the script. It sees where actors get their roles (assigned values), and where they disappear (become null). But it can't peek into reality (program execution)—it can only guess from the script!
Static flow analysis is the process where the compiler checks your code before running the program, trying to figure out if someone is about to play the "NullReferenceException crash" scene.
The gist of it
Starting with C# 8.0 and the Nullable Reference Types (NRT) mode, the compiler checks if a variable could be null in any execution branch. If yes—it tells you right away. It won't let you "use the name of an actor who suddenly turned into a ghost."
2. How to read compiler warnings
Main categories of warnings
When NRT mode is on, the compiler pays extra attention to reference variables (string, User, arrays, objects). Here are the most common warning types:
- Possible null assignment — you're trying to assign a value that could be null to a variable that doesn't allow null.
- Dereference of a possibly null reference — you're accessing a member of an object, but the compiler isn't sure it's not null.
- Possible null return — a method might return null even though its signature says it must return a value.
- Nullable object must have a value — you're using .Value on a nullable type without checking it first.
- Unassigned non-nullable property — you didn't assign a value to a field/property that can't be null.
Usually in your IDE (like Rider or Visual Studio), these warnings are underlined with a squiggly line, and if you hover your mouse, you'll see an explanation.
Example of a typical warning
#nullable enable
string? possibleNull = GetString();
// Uh-oh, compiler complains: "Dereference of a possibly null reference"
int length = possibleNull.Length; // Warning!
The compiler isn't sure that possibleNull isn't null, so accessing .Length could throw an exception.
If your method looks like this:
string? GetString() { ... }
And you write:
string nonNullable = GetString();
You'll get a warning: possible unsafe assignment of null to a non-nullable variable.
3. The compiler analyzes your code
Example 1: straight-up assignment
string? name = null;
Console.WriteLine(name.Length); // Warning: possible null reference
Pretty obvious here: name is null, so trying to access its property is risky.
Example 2: null check
string? name = GetUserName();
if (name != null)
{
Console.WriteLine(name.Length); // All good!
}
else
{
Console.WriteLine("Name not provided!");
}
The compiler gets that inside the if (name != null) block, the variable is safe. That's flow analysis—tracking values depending on conditions.
4. The compiler can't peek "inside the method"
It only looks at the signature—if it says the method returns string?, it trusts that it could be null, even if it always returns a string inside.
Example 3: returning a value from a method
string? GetMaybeName(bool useName)
{
if (useName)
return "Code Jedi";
else
return null;
}
void PrintLength()
{
string? name = GetMaybeName(false);
Console.WriteLine(name.Length); // Warning!
}
The compiler sees that GetMaybeName could return null, so it wants you to be careful.
5. Common scenarios and warnings
Assigning nullable to non-nullable variable
string? maybeUser = GetUser();
string alwaysUser = maybeUser; // warning: possible null assignment
To fix the warning:
string alwaysUser = maybeUser ?? "Guest";
Now you'll always have a string—either from the method or "Guest".
Forgot to check .HasValue on a nullable value type
int? age = GetAge();
int realAge = age.Value; // warning: possible null reference
You should do it like this:
if (age.HasValue)
Console.WriteLine(age.Value);
Or:
int realAge = age ?? -1;
Auto-properties without initialization
class User
{
public string Name { get; set; } // warning: not initialized!
}
Ways to fix:
public string Name { get; set; } = "No name";
or
public string? Name { get; set; }
6. Tricky flow analysis cases
Analysis in loops
string? text = null;
while (text == null)
{
text = Console.ReadLine();
}
Console.WriteLine(text.Length); // all good: compiler figured it out!
Different assignment paths
string? name;
if (Random.Shared.Next() % 2 == 0)
name = "Alice";
else
name = null;
Console.WriteLine(name.Length); // warning: possible null dereference
The compiler checks all branches. If there's null in at least one—it warns you.
7. How to "calm down" the compiler (and when you shouldn't)
Sometimes you're sure the value can't be null, but the compiler doesn't buy it:
string? value = GetSomething();
if (value == null)
throw new Exception("Expected a value!");
Console.WriteLine(value.Length); // Warning goes away
Or you use the ! operator:
string? value = GetSomething();
Console.WriteLine(value!.Length); // No warnings, but risky
This is called the null-forgiving operator. It doesn't check anything—it just tells the compiler: "chill, I've got this." But if you're wrong—you'll get an exception at runtime.
GO TO FULL VERSION