NullCheckAfterDeclaration
Summary
Do not declare a local variable and then check to see whether it is null:
var foo = …;
if (foo is null)
{
⋮
}
Instead, use pattern matching (with a declaration pattern [1]) as follows:
if (… is not {} foo)
{
⋮
}
Default severity
Info
Description
This rule reports diagnostics when a local variable of a reference type is
declared and immediately subjected to a null check. With a declaration pattern,
you can declare a new local variable initialized with a nullable value or
reference and then check whether the value is null
.
When the local variable declaration is an explicit type declaration and has multiple declarators, this analyzer only covers the last variable.
This analyzer will only issue diagnostics if all of the following conditions are true:
- the type of the declared local variable is a nullable reference type
- the expression of the initial value is nullable† (when the nullable context is enabled)
- the local variable is unused when it may be
null
† If it is not
null
, the null check following the declaration makes no sense.
Note that the default diagnostic severity of this analyzer is Information.
How the code fix changes the meanings
Consider the following code:
var foo = GetStringOrNull();
if (foo is null)
{
// foo is null here
⋮
}
else
{
// foo is not null here
⋮
}
// foo is maybe null here (*1)
⋮
The return value of function GetStringOrNull()
is of string?
type:
string? GetStringOrNull() => …;
The type of foo
is string?
, which is a nullable reference type. After the
if
statement (*1), if foo
is not assigned another reference in the then
and else clauses of the if
statement, and if return
, break
, continue
,
throw
, etc. are not in them, then foo
after the if
statement is maybe
null
(i.e., foo
may or may not be null
).
Next, consider the following code that the code fix provider substituted:
if (GetStringOrNull() is not {} foo)
{
// foo is unassigned here
⋮
}
else
{
// foo is assigned (and not null) here
⋮
}
// foo is unassigned here (*2)
⋮
The type of foo
is string
, which is a non-nullable reference type. After the
if
statement (*2), foo
is unassigned. If the state is assigned or
unassigned, it is finally unassigned.
Before the substitution, when foo
may be null
, using foo
as a non-null
reference raises a warning like CS8604. But after the substitution, it raises an
error CS0165 (use of unassigned local variable). That is, this code fix changes
the null
variable to the unassigned one. In other words, null
or not null
changes to unassigned or not null
. Using a local variable that may be null
doesn't cause an error, but using an unassigned local variable does. So this
analyzer must ignore those cases where the code fix causes errors.
🚩 The IDE0019 analyzer in Visual Studio 2022 works the same way.
For example, the following code raises a warning CS8604, so this analyzer does not cover it:
string? file = Environment.GetEnvironmentVariable("FILE");
if (file is null)
{
}
// The following line causes a warning CS8604
File.ReadAllText(file);
Adding a throw
statement or an assignment of non-null
reference to file
at
the last of the then clause eliminates CS8604 so that this analyzer raises the
diagnostic. It raises a diagnostic against the following code:
string? file = Environment.GetEnvironmentVariable("FILE");
if (file is null)
{
throw new Exception();
}
// file is not null here
File.ReadAllText(file);
And the code fix is also available. It also works against the following code:
string? file = Environment.GetEnvironmentVariable("FILE");
if (file is null)
{
file = "default.txt";
}
// file is not null here
File.ReadAllText(file);
Code fix
The code fix provides an option to replace the declaration of the local variable followed by the null check with the corresponding declaration pattern.
There are combinations of implicit and explicit types for declarations, and
null
and not null
for conditions. The following two tables show the
relationship between those combinations and code fixes.
Declaration | Condition | Code Fix |
---|---|---|
var foo = …; |
NotNull | if (… is {} foo) |
var foo = …; |
Null | if (… is {} not foo) |
Bar? foo = …; |
NotNull | if (… is Bar foo) |
Bar? foo = …; |
Null | if (… is not Bar foo) |
Condition | Code |
---|---|
NotNull | (foo is not null) , (foo is {}) , or (foo != null) |
Null | (foo is null) , (foo is not {}) , or (foo == null) |
Note that foo
represents the name of a local variable, and Bar
represents
the type name. Conditions containing the logical negation operator (!
) are
not covered (e.g., (!(foo == null))
, (!(foo is null))
).
Remarks
It can be a breaking change to replace the expression … == null
or … != null
when these operators are overridden. For more information, refer to the
description of EqualsNull code fix.
Example
Diagnostic
string? GetStringOrNull() => …;
var foo = GetStringOrNull();
if (foo is null)
{
⋮
}
string? bar = GetStringOrNull();
if (bar is null)
{
⋮
}
Code fix
string? GetStringOrNull() => …;
if (GetStringOrNull() is not
{
} foo)
{
⋮
}
if (GetStringOrNull() is not string bar)
{
⋮
}
References
[1] Microsoft, Pattern matching - the is and switch expressions, and operators and, or, and not in patterns