Flags Enum Zero Member Named None

ID

csharp.flags_enum_zero_member_named_none

Severity

high

Remediation Complexity

trivial

Remediation Risk

medium

Remediation Effort

low

Resource

Naming Convention

Language

CSharp

Tags

api-design, enum, flags, naming

Description

Reports the zero-valued member of an enum decorated with [Flags] when that member is not named None, whether the zero is written out as = 0 or comes from implicit numbering.

Rationale

A value of a bit-field enum is a set of bits, so zero is the empty set: no flag is set. None is the name the framework design guidelines reserve for it, and readers of a [Flags] enum rely on that convention to tell the empty set apart from a real state.

There is a concrete trap behind the naming. HasFlag asks whether the argument’s bits are all present, and the empty set is present in every value — so value.HasFlag(zeroMember) is unconditionally true. A zero member named after a domain state, such as Default or Unknown, therefore reads as a state one can test for, while every test succeeds.

Two shapes count as the zero member: an explicit = 0 anywhere in the list, and — in an enum where no member is initialized at all — the first member, which implicit numbering puts at zero. The second is the worse of the two, because a [Flags] enum written without explicit values is already broken: 0, 1, 2, 3 are not disjoint bits, so the values overlap and the missing None is simply the visible symptom.

An enum that mixes initialized and uninitialized members is left alone; deciding which member lands on zero there would mean folding the constants of the whole list, and a wrong answer would rename the wrong member.

using System;

[Flags]
public enum FilePermissions
{
    Default = 0,        // FLAW — zero means "no flags", not a default state
    Read = 1,
    Write = 2
}

[Flags]
public enum Permission
{
    Read,               // FLAW — implicitly zero, and the values are not disjoint bits either
    Write,
    Execute
}

[Flags]
public enum NetworkPermissions
{
    None = 0,           // OK, the conventional name for the empty set
    Connect = 1,
    Listen = 2
}

public enum Status        // OK, not a Flags enum — zero is an ordinary member
{
    Unknown = 0,
    Active = 1
}

Remediation

Rename the zero-value member to None. Where the old name carried meaning that callers depend on, keep that meaning as a separate non-zero member, or expose it as a named combination of the real flags.