Zero-valued member of a Flags enumeration is not named None

ID

vbnet.maintainability.flags_enum_zero_member_named_none

Severity

high

Remediation Complexity

trivial

Remediation Risk

medium

Remediation Effort

low

Resource

Type Design

Language

VB.NET

Description

Reports a member with the value zero, declared in an enumeration marked <Flags>, whose name is anything other than None. Both the decimal literal 0 and the hexadecimal &H0 forms are reported, and the attribute is recognised as <Flags>, <Flags()>, <FlagsAttribute()> or a qualified equivalent, alone or alongside other attributes on the same declaration. Enumerations that are not marked <Flags> are not examined - a zero member named Unknown or Unspecified is entirely conventional there - and neither is a <Flags> enumeration that declares no zero member at all.

Rationale

A <Flags> enumeration is a set, and its members are the bits that can be present in that set. Zero is the only value that names no bit, so it does not mean "this particular flag" - it means "no flags", the empty set. None is the name the framework, the base class library and every reader of the type expect for that value, and the expectation is load-bearing rather than cosmetic: Enum.HasFlag and the equivalent (value And member) = member test are always true against a zero member, so code written as if scope.HasFlag(AuditScope.NotAudited) reads as a membership test but is a tautology, and the branch it guards runs for every value including a fully populated one. Naming the member None makes the mistake visible at the call site, because HasFlag(None) is obviously not a question worth asking, whereas HasFlag(Empty) or HasFlag(NotAudited) looks like a legitimate check. The name also drives the framework’s own behaviour: ToString on a zero value emits the member’s name, and Enum.Parse accepts it, so the chosen name leaks into logs, serialized payloads and configuration files, and renaming it after those exist is a breaking change for anything that round-trips the text.

The following code illustrates the pattern detected by this rule:

Namespace Acme.Storage

    <Flags>
    Enum FileAccessRights
        ' FLAGGED: Zero-valued member of a Flags enumeration is not named None
        Empty = 0
        Read = 1
        Write = 2
        Delete = 4
    End Enum

    <Flags>
    Public Enum NotificationChannels

Remediation

Rename the zero-valued member to None. Keep the numeric value at zero so existing persisted values continue to deserialize, and treat the rename as a source-compatible but text-breaking change: update any configuration, serialized payload or query that stores the member by name. If the enumeration genuinely needs a distinct "not yet determined" state as well as an empty set, that state does not belong in a flags enumeration - model it separately, since it is not a bit that can be combined with the others.

' Before: HasFlag(FileAccessRights.Empty) is always True
<Flags>
Public Enum FileAccessRights
    Empty = 0
    Read = 1
    Write = 2
End Enum

' After
<Flags>
Public Enum FileAccessRights
    None = 0
    Read = 1
    Write = 2
End Enum

Configuration

This detector does not need any configuration.