BRO1133: Check for null in one form¶
Null checks are written one way in the whole codebase: x is null and x is not null (the default), or x == null
and x != null. Not a StyleCop rule.
Example¶
Before¶
public class Cache
{
private object entry;
public bool IsEmpty => entry == null;
public object Get(object fallback)
{
if (entry != null)
{
return entry;
}
return fallback;
}
}
After¶
public class Cache
{
private object entry;
public bool IsEmpty => entry is null;
public object Get(object fallback)
{
if (entry is not null)
{
return entry;
}
return fallback;
}
}
Why¶
Both forms are common, and a codebase that mixes them makes the reader wonder whether the difference matters (it
does when the type defines its own ==, which this rule leaves alone). The default is the form that never calls a
user-defined ==, so a null check means the same whatever the type; == null is common too (most of the reference
repositories StyleBro is tested on use it), so it's an option. See decisions.md.
How the fix works¶
Only the operator changes: == becomes is, != becomes is not, and back. With pattern_matching (the default),
null == x becomes x is null (there is no null is x); with equality_operator it is left to
BRO1103, which puts the constant on the right.
Configuration¶
[*.cs]
# pattern_matching (default): x is null, x is not null
# equality_operator: x == null, x != null
stylebro_null_check_style = pattern_matching
Not reported¶
Checks whose meaning or validity would change:
- A user-defined
==on the operand's type (a class withoperator ==, records):x is nullnever calls it, so the result could differ. The core library's operators (string,Type, delegates) are null-safe and are converted, and so are nullable value types (int?,S?with a lifted operator): comparing them with null only readsHasValue. g is nullon a nullable value type whose==comes from the type (Guid?, a struct withoperator ==):g == nullonly readsHasValue, but it names the type's operator, which a banned-API list may forbid (Jellyfin bansGuid's==).int?and other built-in types are converted.dynamicoperands: their==is chosen at run time.- Inside expression trees (
Expression<Func<...>>lambdas, query clauses overIQueryable): patterns don't compile there. x is not nullbefore C# 9 andx is nullbefore C# 7 (the project'sLangVersion). A file compiled more than once (several target frameworks, or linked into another project) is fixed only when its LOWEST version allows it: a multi-targeted project's net472 build uses C# 7.3 unlessLangVersionis set (LibGit2Sharp's tests stopped compiling before). The warning stays in the copies with a newer version.- Operands that aren't null-able: non-nullable value types (
i == nullfor anint, already a compiler warning), type parameters constrained tostruct. Unconstrained type parameters are converted: both forms compile and mean the same. - Other comparisons:
x == default,x == default(T), patterns other than a plainnull(is not (null),is null or "",is { }). - Where the new form would bind differently:
x == nullwith an operand containing an operator that binds looser thanis(a + b == null,o as T == null), andx is nullas an operand of==,<,+and the like (b == o is nullwould become(b == o) == null). - A comment or line break between
isandnot, and a comment betweennulland the operator innull == x.
Compared with StyleCop¶
StyleCop has no such rule. Roslynator's RCS1248 does the same, off by default and with its own option
(roslynator_null_check_style). The .NET SDK has no rule for it: IDE0041 only rewrites ReferenceEquals(x, null),
IDE0150 x is object, IDE0083 !(x is null), and IDE0078 x == null || x == "", all into the pattern form (checked
with dotnet format style, SDK 10.0.401: none of them changes a plain x == null). They point the same way as the
default; with equality_operator, turn them off, or BRO1133 turns their output back on the next run. stylebro-migrate turns this rule off, so
migrating doesn't change a StyleCop-clean repository.