Hi @JJoensuu
Thanks for reaching out.
The rule S4070 doesn’t consider 0xffffffffffffffff
(a.k.a. ulong.MaxValue
) as a combination of all values in the Flags
enum, mainly for two reasons:
- it breaks the property of
Flags
enums that the bitwise OR of all individual values (defined as powers of 2, i.e. values v
for which a non-negative integer e
exists such that v == 1 << e
) should give the All
value
- that is, there is no undeclared individual value in the enum
- it breaks the
F
and G
modifiers of the ToString
method when the bitwise OR of all individual values is passed
As an example, consider the following two enums, declared as [Flags]
:
[Flags]
public enum X1 : ulong
{
None = 0,
A = 1 << 0,
B = 1 << 1,
All = A | B,
}
[Flags]
public enum X2 : ulong
{
None = 0,
A = 1 << 0,
B = 1 << 1,
All = ulong.MaxValue, // 0xffffffffffffffff
}
Here is how X1
and X2
behave:
Console.WriteLine((X1.A | X1.B) == X1.All); // True
Console.WriteLine((X1.A | X1.B).ToString()); // All
Console.WriteLine((X1.All).ToString()); // All
Console.WriteLine((X1.All).ToString("G")); // All
Console.WriteLine((X1.All).ToString("F")); // All
Console.WriteLine((X2.A | X2.B) == X2.All); // False
Console.WriteLine((X2.A | X2.B).ToString()); // A, B
Console.WriteLine((X2.All).ToString()); // All
Console.WriteLine((X2.All).ToString("G")); // All
Console.WriteLine((X2.All).ToString("F")); // All
While X1
respects the properties described above, X2
doesn’t.
As a result X2.A | X2.B
is not X2.All
, so there are least n undeclared values X2.C1
, X2.C2
, …, X2.Cn
such that X2.A | X2.B | X2.C1 | X2.C2 | ... | X2.Cn = X2.All
.
This would create potential bugs in code, such as the following:
void AMethodReceivingX2(X2 x2)
{
if (x2 == X2.All) { /* Do something specific */ }
}
// ...
AMethodReceivingX2(X2.A | X2.B); // I passed all declared flags, yet it's not the same as X2.All!
Therefore, while it may be tempting to conceptually define All
as ~None
for convenience and as it simplifies future maintenance of the enum, we think it’s more correct to:
- either not define the
All
flag explicitly, when not needed
- or define it as the combination of all individual values, when it is needed
So, in your specific scenario, I would change the declaration of the enum in one of the following ways:
// First option
[Flags]
public enum VariantProcessingFeatures : ulong
{
None = 0,
ColorCompression = 1,
}
// Second option
[Flags]
public enum VariantProcessingFeatures : ulong
{
None = 0,
ColorCompression = 1,
All = ColorCompression,
}
I hope that helps,
Best regards,
Antonio