Enum Design
Enums are a special kind of value type. There are two kinds of enums: simple enums and flag enums.
Simple enums represent small, closed sets of choices. A common example of the simple enum is a set of colors. For example,
public enum Color {
Red,
Green,
Blue,
...
}
Flag enums are designed to support bitwise operations on the enum values. A common example of the flags enum is a list of options. For example,
[Flags]
public enum AttributeTargets {
Assembly = 0x0001,
Module = 0x0002,
Cass = 0x0004,
Struct = 0x0008,
...
}
| BRAD ABRAMS
|
| We had some debates about what to call enums that are designed to be bitwise ORed together. We considered bitfields, bitflags, and even bitmasks, but ultimately decided to use flag enums as it was clear, simple, and approachable.
|
| STEVEN CLARKE
|
| I'm sure that less experienced developers will be able to understand bitwise operations on flags. The real question, though, is whether they would expect to have to do this. Most of the APIs that I have run through the labs don't require them to perform such operations so I have a feeling that they would have the same experience that we observed during a recent study—it's just not something that they are used to doing so they might not even think about it.
Where it could get worse, I think, is that if less advanced developers don't realize they are working with a set of flags that can be combined with one another, they might just look at the list available and think that is all the functionality they can access. As we've seen in other studies, if an API makes it look to them as though a specific scenario or requirement isn't immediately possible, it's likely that they will change the requirement and do what does appear to be possible, rather than being motivated to spend time investigating what they need to do to achieve the original goal.
|
Historically, many APIs (e.g., Win32 APIs) represented sets of values using integer constants. Enums make such sets more strongly typed, and thus improve compile-time error checking, usability, and readability. For example, use of enums allows development tools to know the possible choices for a property or a parameter.
- DO use an enum to strongly type parameters, properties, and return values that represent sets of values.
- DO favor using an enum over static constants.
// Avoid the following
public static class Color {
public static int Red = 0;
public static int Green = 1;
public static int Blue = 2;
...
}
// Favor the following
public enum Color {
Red,
Green,
Blue,
...
}
| JEFFREY RICHTER
|
| An enum is a structure with a set of static constants. The reason to follow this guideline is because you will get some additional compiler and reflection support if you define an enum versus manually defining a structure with static constants.
|
- DO NOT use an enum for open sets (such as the operating system version, names of your friends, etc.).
- DO NOT provide reserved enum values that are intended for future use.
- You can always simply add values to the existing enum at a later stage. See section 4.8.2 for more details on adding values to enums. Reserved values just pollute the set of real values and tend to lead to user errors.
public enum DeskType {
Circular,
Oblong,
Rectangular,
// the following two values should not be here
ReservedForFutureUse1,
ReservedForFutureUse2,
}
- AVOID publicly exposing enums with only one value.
- A common practice for ensuring future extensibility of C APIs is to add reserved parameters to method signatures. Such reserved parameters can be expressed as enums with a single default value. This should not be done in managed APIs. Method overloading allows adding parameters in future releases.
// Bad Design
public enum SomeOption {
DefaultOption
// we will add more options in the future
}
...
// The option parameter is not needed.
// It can always be added in the future
// to an overload of SomeMethod().
public void SomeMethod(SomeOption option) {
...
}
- DO NOT include sentinel values in enums.
- Although they are sometimes helpful to framework developers, they are confusing to users of the framework. Sentinel values are values used to track the state of the enum, rather than being one of the values from the set represented by the enum. The following example shows an enum with an additional sentinel value used to identify the last value of the enum, and intended for use in range checks. This is bad practice in framework design.
public enum DeskType {
Circular = 1,
Oblong = 2,
Rectangular = 3,
LastValue = 3 // this sentinel value should not be here
}
public void OrderDesk(DeskType desk){
if((desk > DeskType.LastValue){
throw new ArgumentOutOfRangeException( );
}
...
}
- Rather than relying on sentinel values, framework developers should perform the check using one of the real enum values.
public void OrderDesk(DeskType desk){
if(desk > DeskType.Rectangular || desk < DeskType.Circular){
throw new ArgumentOutOfRangeException( );
}
...
}
| RICO MARIANI
|
You can get yourself into a lot of trouble by trying to be too clever with enums. Sentinel values are a great example of this: People write code like the above but using the sentinel value LastValue instead of Rectangular as recommended. When a new value comes along and LastValue is updated, their program "automatically" does the right thing and accepts the new input value without giving an ArgumentOutOfRangeException. That sounds grand except for all that we didn't show, the part that's doing the actual work, and might not yet expect or even handle the new value. The less clever tests will force you to revisit all the right places to ensure that the new value really is going to work. The few minutes you spend visiting those call sites will be more than repaid in time you save avoiding bugs.
|
- DO provide a value of zero on simple enums.
- Consider calling the value something like "None." If such value is not appropriate for this particular enum, the most common default value for the enum should be assigned the underlying value of zero.
public enum Compression {
None = 0,
GZip,
Deflate,
}
public enum EventType {
Error = 0,
Warning,
Information,
...
}
- CONSIDER using
Int32 (the default in most programming languages) as the underlying type of an enum unless any of the following is true:
- The enum is a flags enum and you have more than 32 flags, or expect to have more in the future.
| BRAD ABRAMS
|
This might not be as uncommon a concern as you first expect. We are only in version 2.0 of the .NET Framework and we are already running out of values in the CodeDom GeneratorSupport enum. In retrospect, we should have used a different mechanism for communicating the generator support options than an enum.
|
| RICO MARIANI
|
Did you know that the CLR supports enums with an underlying type of float or double even though most languages don't choose to expose it? This is very handy for strongly typed constants that happen to be floating point (e.g., a set of canonical conversion factors for different measuring systems). It's in the ECMA standard.
|
- The underlying type needs to be different than
Int32 for easier interoperability with unmanaged code expecting different size enums.
- A smaller underlying type would result in substantial savings in space. If you expect for enum to be used mainly as an argument for flow of control, the size makes little difference. The size savings might be significant if:
- You expect the enum to be used as a field in a very frequently instantiated structure or class.
- You expect users to create large arrays or collections of the enum instances.
- You expect a large number of instances of the enum to be serialized.
For in-memory usage, be aware that managed objects are always DWORD aligned so you effectively need multiple enums or other small structures in an instance to pack a smaller enum with to make a difference, as the total instance size is always going to be rounded up to a DWORD.
| BRAD ABRAMS
|
Keep in mind that it is a binary breaking change to change the size of the enum type once you have shipped, so choose wisely with an eye on the future. Our experience is that Int32 is usually the right choice and thus we made it the default.
|
- DO name flag enums with plural nouns or noun phrases and simple enums with singular nouns or noun phrases.
- DO NOT extend
System.Enum directly.
-
System.Enum is a special type used by the CLR to create user-defined enumerations. Most programming languages provide a programming element that gives you access to this functionality. For example, in C# the enum keyword is used to define an enumeration.