Skip to content

BRO1148: Check a nullable value type for null instead of calling HasValue

x.HasValue becomes x is not null and !x.HasValue becomes x is null, or x != null and x == null, whichever form BRO1133 asks for. Not a StyleCop rule: from Meziantou's MA0171. Off by default, also in the preset.

Example

root = true

[*.cs]
dotnet_diagnostic.BRO1148.severity = warning

Before

using System;

public class Booking
{
    public DateTime? CheckedIn { get; set; }

    public DateTime? CheckedOut { get; set; }

    public bool IsActive => CheckedIn.HasValue && !CheckedOut.HasValue;
}

After

using System;

public class Booking
{
    public DateTime? CheckedIn { get; set; }

    public DateTime? CheckedOut { get; set; }

    public bool IsActive => CheckedIn is not null && CheckedOut is null;
}

Why

HasValue and a null check mean the same on a nullable value type (the compiler emits the same HasValue call for both), so a codebase that writes null checks one way can write them that way for int? too, and a reader doesn't have to know whether x is a reference or a nullable value. It's a matter of taste, though: many teams prefer HasValue, which says "nullable value type" at a glance. That's why the rule is off by default.

Configuration

The form comes from BRO1133's option, so the two rules never disagree:

[*.cs]
# pattern_matching (default): x is not null / x is null; equality_operator: x != null / x == null
stylebro_null_check_style = pattern_matching

How the fix works

It replaces .HasValue with is not null (and removes the ! before a negated one), leaving the expression in front as it is. The result is already BRO1133's preferred form, so dotnet format needs one run with both rules on.

Not reported

  • Places where the null check would bind differently: as an operand of an operator that binds as tightly as == or tighter (b == x.HasValue, !!x.HasValue), or as the receiver of a member (x.HasValue.ToString()). The same places BRO1133 leaves alone. While BRO1405 is on, parentheses around x.HasValue don't count (it removes them).
  • Patterns where they don't compile: is not null before C# 9, is null before C# 7, and patterns in expression trees (Expression<Func<int?, bool>> f = n => n.HasValue;; n != null is fine there).
  • != null that would call a user-defined operator: on Guid?, DateTime? or DateTimeOffset?, x != null names the underlying type's operator, lifted (harmless at run time, but a banned-API list may forbid it; BRO1133 skips the same).
  • Other HasValue properties (only System.Nullable<T>.HasValue), nameof(x.HasValue), and comments, line breaks or spaces around the . or after the !.

Compared with StyleCop

StyleCop has no such rule (its issue #1455 asked for HasValue -> != null; never implemented). stylebro-migrate leaves it off.