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
}