Design Guidelines Update: Enums vs Boolean Arguments

This was a pretty heavily debated guideline internally, I think we reached a good conclusion... Your feedback welcome as always! 

The full guidelines can be found here, we will be roll this (and other updates) into it for the whidbey release.

Enums vs Boolean Arguments

A framework designer is often faced with the choice of when to use enums and when to use Booleans for method and constructor arguments. In general terms you should favor using enums where it makes the readability of source code more explicit and therefore easier to understand. Anywhere using enums would add unneeded complexity and actually hurt readability of source code Booleans should be preferred.

            Do use enums if you two or more arguments that are Booleans.

Enums are much more readable (in books, documentation, code reviews, etc). Consider a method call that looks such as:

FileStream f = File.Open (“foo.txt”, true, false);

This call gives you no context whatsoever to understand the meaning behind true and false. Now consider if the call where:

FileStream f = File.Open (“foo.txt”, CasingOptions.CaseSenstative, FileMode.Open);

Annotation: Some have asked why we don’t have a similar guideline for integers, doubles, etc, should we find a way to “name” them as well? There is a big difference between numeric types and Booleans: you almost always use constants and variables to pass numerics around, because it is good programming practice and you don’t want to have “magic numbers”. However, if you take a look at any managed code-base, this is almost never true of Booleans. 80% of the time a Boolean argument is passed in as a constant, and its intention is to turn a piece of behavior on or off. We could alternatively try to establish a coding guideline that you should never pass a primitive “true/false” value to a method or constructor, but we would probably get significant pushback on that. I certainly don’t want to define a constant for each Boolean parameter I’m passing in.

Enums are much easier for developers to work with in Intellisense and in documentation. Often a good parameter name can give you a hint as to when to pass true or false, but parameter names are not generally available when reading code. enums leave little ambiguity by making very clear which value to pass when writing and reading code.

Annotation: Methods with two Boolean parameters (like the one in the example above) allow developers to inadvertently switch the arguments, and the compiler and static analysis tools can't help you. Even with just one parameter, I tend to believe it's a somewhat easier to make a mistake with Booleans ... let's see, does true mean "case insensitive" or "case sensitive"?

 

            Do use enums unless you are 100% sure there will never be a need for another option.

Enums have the potential to give you more room for future expansion but be aware of versioning issues specific to enums in section x.y.

Annotation: We have seen a couple of places in the framework where we added a Boolean in V1 and in V2 we were forced to add another Boolean option to account for what could have been a foreseeable change. Don’t let this happen to you, if there is even a slight possibility of needing a more options in the future use an enum now.

 

            Avoid using enums for method\constructor arguments for single arguments that are truly two state values. If the member does not take more than one Boolean argument and there are now and forever more only two states.

 

Annotation: There are a number of reasons to avoid introduction of more enums than necessary to meet the spirit of this guideline. Most of the problems manifest themselves when you use properties that return Enums which is not directly covered by this guideline. Examples include:

 

It is difficult to “interop” what are essentially Boolean values that are expressed as independent enums. For instance, if I want to do something like underline something that is not bold, I want to be able to do the following:

    myStyle.Underline = !myStyle.Bold;

This works because both underline and bold are two-state, and will always be two-state (there are always examples where this is not the case, and in such cases, enums should be used). Note that if I were to use enums, I’d need to write:

 

    if (myStyle.Boldness == BoldStyle.Bold)

    {

        myStyle.Underline = UnderlineStyle.NoUnderline;

    }

    else

    {

        myStyle.Underline = UnderlineStyle.Underline;

    }

 

or

 

    myStyle.Underline =

       (myStile.Boldness == BoldStyle.Bold ? UnderlineStyle.NoUnderline : UnderlineStyle.Underline);

 

neither of which are particularly appealing.

 

If the enum is returned from a property, then the consumer of that property doesn’t really know whether or not they need to consider whether or not the enum is going to have additional states in later versions. They don’t know that it’s now and forevermore a two-state value. Therefore, they don’t know what to do in the “default” section of their switch statement that consumes this value. As an example of this, the writer of the code in the point above may not know that BoldStyle is truly and forevermore a two-state value. To protect themselves for versioning’s sake, they won’t even be able to write the above, they’ll need to go even further to:

 

    switch (myStyle.Boldness)

    {

        case BoldStyle.Bold:

            myStyle.Underline = UnderlineStyle.NoUnderline;

            break;

 

        case BoldStyle.NotBold:

            myStyle.Underline = UnderlineStyle.Underline;

     break;

 

        default:

            // something else, appropriate for versioning of this code.

            break;

    }

As an even more extreme, but very real, example of the issue described at top, consider the databinding scenario where one wants to databind two values that would otherwise be Booleans together. For example, binding the “bold-ness” of one instance to the “underline-ness” of another. Here, the problem is exacerbated because writing the code that would do the conversion between enum values is much less simple… that is, there’s typically not a chunk of code to add such a conversion into. What is required is the addition of a data transformer into the data binding evaluation that will do the transformation. Same problem and issue, just a much more elaborate workaround required.

 

Hence, because of these difficulties in using enums when Booleans sufficiently describe the data, API designers should think hard about whether they really require multiple Boolean parameters to a method. If they can avoid such method signatures, they should be able to avoid introducing a new enum when a Boolean will do.

 

Annotation: An interesting clarification of this guideline for constructor parameters that map onto properties is that if the value is typically set in the constructor an enum value is better otherwise a Boolean value is better. This thinking helped us clarify a recent CodeDom work item to add “IsGlobal” on CodeTypeReference. In this case it should be an enum because it is typically set in the constructor, but the “IsPartial” property on CodeTypeDeclaration should be a Boolean.

 

            Avoid sharing of enum types between different families of methods. Although it may seem like similar concepts unless there represent the exact same concept there will be issues as they diverge in the future.

 

Annotation: Generally speaking, enums don’t cost noticeably more than Booleans. As far as the runtime is considered an enum is treated just like its underlying type which is in most cases Int32 (the same in memory size Booleans take up). Today there is a very small overhead (on the order of 300 bytes for each enum type referenced) but this at the noise range and should not be considered a factor in choosing to use a Boolean over an enum.

It is possible to write bad code with enums that does perform poorly; you should see section x.y on argument validation for a note on avoiding calls to Enum.IsDefined as it loads reflection which is often performance issue.