Enumerations bridge the gap between numbers and objects. More than a numerical ID, less than a struct, they fill that niche where you want a well-understood name without any extra baggage. You can think of them as (mostly) type-safe constants.
Enumerations also provide type-safe functionality for flags.
Solution: Enumerations can be defined within namespaces or classes with this syntax:
This code declares a BookBinding
enumeration containing three values. The first value is None
and is often included to indicate the lack of a value.
You can use the enumeration like this:
BookBinding binding = BookBinding.Hardcover;
Solution: Simply assign values inside the enumeration. You don’t have to define every value. You can define some of them and the compiler will fill in the rest sequentially.
There is nothing preventing you from assigning duplicate values to the enumeration constants. I’m not sure if there is a legitimately useful reason to do this, but it is legal.
Solution: In C++, you would #define
constants, often at a global level. In C#, you can use enumerations to achieve bit-field flag functionality in a type-safe way. You do need to explicitly set the values to multiples of two, however.
You can use the BookGenres
enumeration in bit-field operations. To see what [Flags]
does, examine the following code and its output:
Note that the enumeration with the [Flags]
attribute printed out each genre correctly.
Solution: The standard way of doing this is to use the bitwise & operator:
However, .NET 4 introduces the HasFlag()
method, which accomplishes the same thing:
bool isVampire = genres.HasFlag(BookGenres.Vampire);
Solution: Enumerations cannot be implicitly converted to integers (and vice versa) like they can in C++. You need to explicitly cast the values.
int value = (int)BookLanguage.English;
BookLanguage lang = (BookLanguage)value;
When converting from integer to an enumeration, make sure you validate the result (see the next section).
Given the preceding enumeration definition, the following is still valid code:
BookBinding = (BookBinding)9999;
Therefore, you will need to validate enumeration values wherever necessary.
Solution: .NET provides the Enum.IsDefined()
method to do this.
Solution: To get a list of all defined values for a specific enumeration, call Enum.GetValues()
, as shown here:
Enum.GetName()
returns the same string you would get if you just called ToString()
on a value. You can also just call Enum.GetNames()
to get all the strings back directly.
Solution: While Enum
does contain a standard Parse()
method, this can throw exceptions and requires you to cast the result to the desired enumeration type. Instead, use TryParse()
, which is both safe and strongly typed, using generics, as in this code example:
Solution: Enum.TryParse()
also works for flags, and it will handle duplicate values as well, as in this sample:
The following output is produced:
Solution: Using extension methods combined with attributes (see Chapter 24, “Reflection and Creating Plugins”), you can add a method to your enumeration to access the values you attached in attributes. For this example, let’s create our own attribute called Culture
, which we’ll attach to our BookLanguage
enumeration values.
Now we’ll modify our enumeration to include these attributes:
Finally, we need to add the extension method to access these attributes:
The following output is produced:
Before adding too much extra data on top of Enum
values, make sure that you are not violating their lightweight nature. Consider if you actually need a struct
to represent your data (perhaps with the Enum as an ID in the struct).
The following list contains some best practices you should follow when using enumerations:
• If your enumerations must match external values (such as from a database), then explicitly assign a value to each enumeration member.
• Always use [Flags]
when you need to combine multiple values into a single field.
• Flag enumerations must have values explicitly assigned that are unique powers of two (1, 2, 4, 8, 16, and so on) to work correctly.
• An enumeration should have a singular name if it is not used as flags, whereas flags should have plural names. See the BookBinding
and BookGenres
examples earlier in this chapter.
• Define a None
value in each enumeration, with value of 0. This is especially important when using [Flags]
.