MSDN Magazine > Issues and Downloads > 2001 > October >  .NET: Extolling the Virtues of Enumerated Types
.NET: Extolling the Virtues of Enumerated Types
MSDN Magazine
Extolling the Virtues of Enumerated Types
Jeffrey Richter
M
ost programmers are already familiar with enumerated types. But just to review, an enumerated type defines a set of symbolic names and values. For example, the Color type shown here defines a set of symbols, each symbol identifying a single color:
enum Color {
   Red,       // Assigned a value of 0
   Green,     // Assigned a value of 1
   Blue,      // Assigned a value of 2
   Orange     // Assigned a value of 3
}
      Of course, a programmer can always write a program using 0 to represent red, 1 to represent green, and so on. However, there are at least two reasons why you shouldn't hardcode numbers into their code and should instead use an enumerated type. First, enumerated types make the program much easier to write, read, and maintain. This is because the symbolic name is used throughout the code and the programmer doesn't have to make the mental mapping that red is 0 or that 0 means red. In addition, documentation tools and other utilities (such as a debugger) can show meaningful symbolic names to the programmer. Second, enumerated types are strongly typed. For example, the compiler will report an error if I attempt to pass Color.Orange as a value to a method requiring a Fruit enumerated type as a parameter.
      In the common language runtime (CLR), enumerated types are more than just symbols that the compiler cares about. Enumerated types are treated as first-class citizens in the type system, and this lets you perform powerful operations that simply can't be done with enumerated types in other environments (like unmanaged C++, for example).
      Every enumerated type inherits directly from System.Enum, which inherits from System.ValueType, which inherits from System.Object. So, enumerated types are value types and may be represented in unboxed and boxed forms. However, unlike other value types, you cannot declare any methods, properties, or events on an enumerated type.
      When an enumerated type is compiled, the compiler automatically turns each symbol into a constant field of the type. For example, the compiler treats the Color enumeration shown previously as if you had written code similar to this:
struct Color : System.Enum {
   public const Color Red    = (Color) 0;
   public const Color Green  = (Color) 1;
   public const Color Blue   = (Color) 2;
   public const Color Orange = (Color) 3;
}
      The C# compiler will not actually compile the previous code, but it does give you an idea of what's happening internally. Basically, an enumerated type is just a structure that has a bunch of constant fields defined in it. The fields are emitted out to the module's metadata and can be accessed via reflection. This means that you can get all of the symbols and their values associated with an enumerated type at runtime. It also means that you can convert a string symbol into its equivalent numeric value.
      To simplify these operations and to allow you to avoid having to become familiar with reflection, the System.Enum base type offers several static and instance methods that expose the special operations that can be performed on an instance of an enumerated type.
      For example, the Enum type has a static method called GetUnderlyingType:
static Type GetUnderlyingType(Type enumType);
This method returns the core type used to hold an enum's value. Every enumerated type has an underlying type, which can be byte, sbyte, short, ushort, int (the most common), uint, long, or ulong. Of course, these C# primitive types correspond to framework class library types. However, the C# compiler requires that you specify a primitive type here; using a base class type (like Int32) generates an error. The following code shows how to declare an enum with an underlying type of byte (System.Byte) using C#:
enum Color : byte {
   Red,
   Green,
   Blue,
   Orange
}
      With the Color enum that I just defined, the following code shows what GetUnderlyingType will return:
// The following line displays "System.Byte"
Console.WriteLine(Enum.GetUnderlyingType(typeof(Color)));
      Given an instance of an enumerated type, it is possible to map that value to one of four string representations using the System.Enum.ToString method:
Color c = Color.Blue;
Console.WriteLine(c.ToString ());   // "Blue" (General format)
Console.WriteLine(c.ToString("G")); // "Blue" (General format)
Console.WriteLine(c.ToString("D")); // "2"    (Decimal format)
Console.WriteLine(c.ToString("X")); // "2"    (heX format)
      Internally, the ToString method calls System.Enum's static Format method:
public static String Format(Type enumType,
    Object value, String format);
Generally speaking, I prefer to call the ToString method because it involves less code and it is easier to call. Format has one advantage over ToString. Format lets you pass a numeric value for the value parameter; you don't have to have an instance of the enumerated type. For example, the following code will display "Blue":
Console.WriteLine(Enum.Format(typeof(Color), 2, "G"))
      You should note that it is possible to declare an enumerated type that has multiple symbols all with the same numeric value. When converting a numeric value to a symbol, Enum's methods return one of the symbols. There is no guarantee as to which symbol name is returned. Also, if there is no symbol defined for the numeric value you're looking up, then a string containing the numeric value is returned.
      It is also possible to create an array that contains one element for each symbolic name in an enumerated type using the System.Enum's static GetValues method:
static Array GetValues(Type enumType);
Using this method along with the ToString method, you can display all of an enumerated type's symbolic and numeric values:
Color[] colors = (Color[]) Enum.GetValues(typeof(Color));
Console.WriteLine("Number of symbols defined: " + colors.Length);
Console.WriteLine("Value\tSymbol\n——\t———");
foreach (Color c in colors) {
   // Display each symbol in Decimal & General format
   Console.WriteLine("{0,5:D}\t{1:G}", c, c);
}
      The previous code produces the following output:
Number of symbols defined: 4
Value   Symbol
-----   ------
0       Red
1       Green
2       Blue
3       Orange 
      This discussion shows some of the cool operations that can be performed on enumerated types. I suspect the ToString method with the general format will be used quite frequently to show symbolic names in a program's UI (as long as the strings don't need to be localized). In addition to GetValues, the Enum type also offers the following two static methods that return an enum's symbols:
// Returns a String representation for the numeric value
public static String   GetName(Type enumType, Object value);

// Returns an array of Strings: 1 per symbol defined in the enum
public static String[] GetNames(Type enumType); 
      So far I've discussed a lot of methods that look up an enum's symbol. But a method that can take a symbol and look up the equivalent value is also needed. This could be used to convert a symbol that a user enters into a textbox, for example. Converting a symbol to an instance of an enumerated type is easily accomplished using Enum's static Parse method:
public static Object Parse(Type enumType, 
   String value, Boolean ignoreCase);
Here's some code demonstrating how to use this method:
// Since Orange is defined as 3, 'c' is initialized to 3.
Color c = (Color) Enum.Parse(typeof(Color), "orange", true);

// Since Brown is not defined, an ArgumentException is thrown
Color c = (Color) Enum.Parse(typeof(Color), "Brown", false);

// Creates an instance of the Color enum with a value of 1
Color c = (Color) Enum.Parse(typeof(Color), "1", false);

// Creates an instance of the Color enum with a value of 23
Color c = (Color) Enum.Parse(typeof(Color), "23", false);
      Finally, using Enum's static IsDefined method, you can determine if a numeric value is legal for an enumerated type:
// Displays "True" because Color defines Green as 1.
Console.WriteLine(Enum.IsDefined(typeof(Color), 1));

// Displays "True" because Color defines Red as 0.
Console.WriteLine(Enum.IsDefined(typeof(Color), "Red"));

// Displays "False" because a case-sensitive check is performed
Console.WriteLine(Enum.IsDefined(typeof(Color), "red"));

// Displays "False" because Color doesn't have a symbol of value 10.
Console.WriteLine(Enum.IsDefined(typeof(Color), 10));
The IsDefined method is frequently used to do parameter validation. For example:
public void SetColor(Color c) {
 if (!Enum.IsDefined(typeof(Color), c)) {
  throw(new ArgumentOutOfRangeException("c", "Not a valid Color"));
 }
 •••
}
The parameter validation is useful because someone could call SetColor like this:
SetColor((Color) 547);
Since there is no symbol with a corresponding value of 547, the SetColor method will throw an ArgumentOutOfRangeException exception, indicating which parameter was invalid and why.
      Many of the methods I've discussed look up the symbols associated with an enumerated type. The first time you call one of these methods, it uses reflection to obtain this information and then this information is cached for the AppDomain. So the first time you call one of these methods, your application incurs a small performance hit, but future calls to these methods (for the same enumerated type) should go quickly.
      Finally, the System.Enum type offers you a set of static ToObject methods that convert an instance of a standard integral type—Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, or UInt64—to an instance of an enumerated type.
      Enumerated types are always used in conjunction with some other type. Typically, they are used for the type's method parameters, properties, and fields. A common question that arises is whether to define the enumerated type nested within the type that requires it, or whether the enumerated type should be defined at the same level as the type that requires it. If you examine the framework class library, you'll see that most enumerated types are defined at the same level as the class that requires it. The reason is simply to make the developer's life a little easier by reducing typing. So, it is recommended that you define your enumerated type at the same level unless you are concerned about name conflicts.

Bit Flag Types

      Programmers frequently work with sets of bit flags. When you call the System.IO.File type's GetAttributes method, it returns an instance of a FileAttributes type. A FileAttributes type is an instance of an Int32-based enumerated type, where each bit reflects a single attribute of the file. Figure 1 shows the FileAttributes type as it's defined in the class library.
      So, to determine if a file is hidden, you would execute code like the following:
String file = @"C:\Boot.ini";
FileAttributes attributes = File.GetAttributes(file);
Console.WriteLine("Is {0} hidden? {1}", file, 
   (attributes & FileAttributes.Hidden) != 0);
And here is code demonstrating how to change a file's attributes to read-only and hidden:
File.SetAttributes(@"C:\Boot.ini", 
   FileAttributes.ReadOnly | FileAttributes.Hidden); 
      As the FileAttributes type shows, it is often the case that enumerated types are used to express the set of flags that can be combined. However, while enumerated types and bit flags are similar, they do not have the exact same semantics. For example, enumerated types represent single numeric values, while bit flags represent a set of flags—some of which are on and some of which are off.
      When defining an enumerated type that is to identify bit flags, you should, of course, explicitly assign numeric values (that map to individual bits) to each of the symbols. It is also highly recommended that you apply the System.FlagsAttribute custom attribute type to the enumerated type, as shown here:
[Flags] // The C# compiler allows either "Flags" or "FlagsAttribute"
enum Actions {
    Read   = 0x0001,
    Write  = 0x0002,
    Delete = 0x0004,
    Query  = 0x0008,
    Sync   = 0x0010 
}
      Since Actions is an enumerated type, all of the methods described in the previous section can be used when working with bit flag enumerated types. However, it would be nice if some of those functions behaved a little differently. For example, let's say you had the following code:
Actions actions = Actions.Read | Actions.Write; // 0x0003
Console.WriteLine(actions.ToString());          // "Read, Write"
When ToString is called, it attempts to translate the numeric value into its symbolic equivalent. The numeric value is 0x0003, which has no symbolic equivalent. However, the ToString method detects the existence of the [Flags] attribute on the Actions type, and ToString now treats the numeric value not as a single value but as a set of bit flags. Since the 0x0001 and 0x0002 bits are set, ToString generates the following string: "Read, Write". If you remove the [Flags] attribute from the Actions type, then ToString would produce the following string: "3".
      I discussed the ToString method in the previous section and I showed that it offered three ways to format the output: "G" (general), "D" (decimal), and "X" (hex). When formatting an instance of an enumerated type using the general format, the type is first checked to see if the [Flags] attributes is applied to it. If this attribute is not applied, then a symbol matching the numeric value is looked up and returned. If the [Flags] attribute is applied, then a symbol matching each 1 bit is looked up and concatenated to a string; each symbol is separated by a comma.
      If you prefer, you could choose to define the Actions types without the [Flags] attributes and still get the correct string by using the "F" format:
// [Flags]     // Commented out now
enum Actions {
    Read   = 0x0001,
    Write  = 0x0002,
    Delete = 0x0004,
    Query  = 0x0008,
    Sync   = 0x0010 
}

Actions actions = Actions.Read | Actions.Write; // 0x0003
Console.WriteLine(actions.ToString("F"));       // "Read, Write" 
      As mentioned in the previous section, the ToString method actually calls System.Enum's static Format method internally. This means that you can use the "F" format when calling the static Format method. If the numeric value contains 1 bits with no matching symbols, then the returned string will contain just a decimal number and none of the symbols will appear in the string. Note that the symbols you define in your enumerated type do not have to be pure powers of two. For example, the Actions type could define a symbol called All with a value of 0x001F. If an instance of the Actions type has a value of 0x001F, then formatting the instance will produce a string that contains "All" (the other symbol strings will not appear).
      So far I've discussed how to convert numeric values into a string of flags. It is also possible to convert a string of comma-delimited symbols into a numeric value by calling Enum's static Parse method. Figure 2 contains some code demonstrating how to use this method.
      Again, when Parse is called, it checks whether the [Flags] custom attribute has been applied to the enumerated type. If the attribute exists, then Parse knows to split the string into individual symbols, look up each symbol and bitwise-OR the corresponding numeric value into the resulting instance of the enumerated type.
      The [Flags] attribute affects how ToString, Format, and Parse behave. Compilers are also encouraged to look for this attribute and ensure that the enumerated type is being manipulated as a set of bit flags. For example, a compiler could only allow bit operations on the bit flag enumerated type and disallow other arithmetic operations such as multiplication and division. The C# compiler ignores the [Flags] attribute completely; anything you can do with an enumerated type you can also do with a bit flag enumerated type.

Send questions and comments for Jeff to dot-net@microsoft.com.
Jeffrey Richter is the author of Programming Applications for Microsoft Windows (Microsoft Press, 1999), and is a cofounder of Wintellect (http://www.Wintellect.com), a software education, debugging, and consulting firm. He specializes in programming/design for .NET and Win32. Jeff is currently writing a Microsoft .NET Framework programming book and offers .NET seminars.

From the October 2001 issue of MSDN Magazine.

Page view tracker