Cutting Edge

All About Enums

Dino Esposito

Code download available at:CuttingEdge2006_08.exe(170 KB)

Contents

Enumeration Essentials
Under the Hood
Working with Enum Types
Binding Enum Values to the UI
A Custom Dropdown List Control
Formatting Enum Values
The Flags Attribute
Enum Values in a Data Access Layer

Among the many benefits of writing magazine articles, I particularly enjoy reader feedback, especially when readers are inspired to try new things and report back with useful development tips. Recently I've had some interesting mail about enumeration types, so I've decided to talk about them here. I'll review the basics of enumeration types and their implementation in the Microsoft® .NET Framework, then look at a reader's real-world use of them.

Enumeration Essentials

An enum type, or enumeration, is a homogeneous collection of named constants. The following is the declaration of the Enum class—the root of all enumerated types:

Public MustInherit Class Enum Inherits ValueType Implements IComparable, IFormattable, IConvertible

Recall that .NET data types are separated into value and reference types. Whereas reference types are always allocated on the common language runtime (CLR) heap, value types are allocated on the stack or as part of a reference type. Both types ultimately derive from the base class Object. Integers, numbers, dates, Booleans, characters, and bytes are all value types, as is the System.Enum type. Only integral types can be used as the underlying type for the constants in an enum type. The full list of valid types includes Byte, SByte, Int32, UInt32, Int16, UInt16, Int64, and UInt64. Note that each programming language provides special syntax to let you declare an enumeration as a set of name/value pairs. Here's how to declare an enum in Visual Basic®:

Public Enum Categories Speaking Training Consulting Mentoring End Enum

If no underlying type is explicitly declared, Int32 (Integer in Visual Basic) is used implicitly. If you want to use constants of a particular type, then you add the type name in the declaration line:

Public Enum Categories As Int64 Speaking Training Consulting Mentoring End Enum

Each entry in an enum represents a value of the specified (or inferred) type. By default, values are assigned consecutively starting from 0. Values can also be set explicitly, both individually and for a group of elements. The following code defines 100 as the starting point for the enumeration. The Training entry will be 101, Consulting will be 102, and so on:

Public Enum Categories As Int32 Speaking = 100 Training Consulting Mentoring End Enum

You could also use the following:

Public Enum Categories As Int32 Speaking Training = 100 Consulting Mentoring = 200 End Enum

In this case, Speaking is 0, Training is 100, Consulting 101, and Mentoring is 200. It is perfectly legal that each element is assigned its own value without any form of regular succession. Moreover, values associated with enum entries don't have to be unique:

Public Enum Categories As Int32 Speaking Training = 100 Consulting Mentoring = 100 End Enum

Although an enum is declared using syntax that does not look at all object oriented, the fact is that in the .NET Framework an enum type is a regular type with its own well-defined inheritance hierarchy. Enum types are not allowed to have an object model exposing their own set of methods, properties, and events. An object model exists, however, on the base class of all enums—System.Enum. Before I examine methods on System.Enum and their usage, let's take a look at enum types under the covers.

Under the Hood

In most .NET-targeted languages, an enum is declared as a collection of named constants. It is interesting to take a look at the intermediate language (IL) code generated for a sample enum as shown in the following:

Public Enum Categories As Int32 Speaking Training Consulting Mentoring End Enum

The disassembled IL for this snippet looks like the code in Figure 1. As you can see, this class extends System.Enum with a sequence of static public fields marked as literals. In particular, this means that compilers will transform any enum values referenced by code into raw values. For example, consider the following piece of Visual Basic code from an ASP.NET sample page:

Sub DropDownList1_SelectedIndexChanged( _ ByVal sender As Object, ByVal e As EventArgs) _ Handles DropDownList1.SelectedIndexChanged Response.Write(Categories.Mentoring) End Sub

Figure 1 IL for the Categories Enum

class public auto ansi sealed Categories extends [mscorlib]System.Enum { .field public static literal Categories Consulting = int32(0x00000002) .field public static literal Categories Mentoring = int32(0x00000003) .field public static literal Categories Speaking = int32(0x00000000) .field public static literal Categories Training = int32(0x00000001) .field public specialname rtspecialname int32 value__ }

The corresponding IL is shown in Figure 2. Look carefully at line L_0006, which pushes the argument for the Response.Write value onto the evaluation stack. As you can see, the argument is an Int32 explicit value (ldc.i4 is used to load a 4-byte integer). Next, in the L_000b statement the box operation pops the argument value from the stack and replaces it with a new object that boxes the numeric value type. This is a necessary step because Response.Write requires an object as its argument.

Figure 2 IL for the Visual Basic Subroutine

.method family instance void DropDownList1_SelectedIndexChanged( object sender, [mscorlib]System.EventArgs e) cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: callvirt instance [System.Web]System.Web.HttpResponse [System.Web]System.Web.UI.Page::get_Response() L_0006: ldc.i4 1000 L_000b: box Categories L_0010: callvirt instance void [System.Web]System.Web.HttpResponse::Write(object) L_0015: ret }

It is also interesting to dig out some internals of the System.Enum class—the parent of all enum types.

As shown earlier, System.Enum implements three interfaces that add specific functionalities, in this case, comparison, formatting, and conversion. The IComparable interface provides a CompareTo method whereas the IFormattable interface's contribution is all in one of the overloads for the ToString method. The IConvertible interface provides a number of ToXXX methods to convert enum values into more specific, mostly numeric, types.

Working with Enum Types

The System.Enum provides a number of methods to list, parse, convert, and compare values, as you can see in Figure 3. Let's take a look at the GetValues method. It is defined as follows:

Public Shared Function GetValues(ByVal enumType As Type) As Array

Simply put, the method returns an array of objects filled with all values associated with the enum. Here's some sample code that shows how to use the GetValues method to grab the values in the Categories enum type:

Dim values As String() = [Enum].GetValues(GetType(Categories))

Figure 3 Important System.Enum Methods

Method Description
CompareTo Instance method; compares two enum values and returns an indication of how they relate.
Format Static method; converts the specified value to its equivalent string representation using the specified format.
GetName Static method; retrieves the name of the constant that has the specified value.
GetNames Static method; retrieves an array of the names of the constants.
GetUnderlyingType Static method; retrieves an array of the names of the constants.
GetValues Static method; retrieves an array of the values of the constants.
IsDefined Static method; returns an indication whether a constant with a specified value exists in the enumeration.
Parse Static method; converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.
ToObject Static method; returns an instance of the enum for a specific value.
ToString Instance method; converts the value of a named constant to an equivalent string representation.

How can this work if all the compiler emits is the code in Figure 1? In other words, the compiler creates a new class that extends System.Enum and adds constant fields. Where does the GetValues method (and similarly the GetNames method) get content for the array it returns?

If you look under the hood of the System.Enum class using a decompiler or disassembler, you'll see that the class internally makes use of a Hashtable synchronized for access. The Hashtable is initialized in the static constructor of the Enum class. Also known as a type initializer, a static constructor is a method that the CLR invokes automatically (if any is defined on a type) to initialize a type. A type initializer in Microsoft intermediate language (MSIL) is a static method named .cctor. Type initializers are useful to ensure that some static code executes before a class is instantiated. The following is Visual Basic pseudocode for the relevant portion of the .cctor method of System.Enum:

Shared Sub New() ... Enum.fieldInfoHash = _ Hashtable.Synchronized(New Hashtable) End Sub

When will the fieldInfoHash internal member be accessed? It is used by most of the methods on System.Enum, that is Parse, GetNames, GetValues, and IsDefined, plus a few other internal and private methods. GetValues, for example, instantiates an array to return to callers and populates it with data from fieldInfoHash.

The Hashtable is populated on-demand through reflection with the values of all the named constants that form the enum. It is worth noting that the GetHashEntry method takes a type parameter; the Hashtable contains information for all enum types being used in the application. The cost of the reflection to populate the Hashtable for a particular enumeration is paid only once per application; any successive access is to cached data. All static methods on the System.Enum class consume cached data with no significant performance costs.

Binding Enum Values to the UI

The main purpose of GetValues is to provide the contents of the enum type in a bindable form. In both ASP.NET and Windows® Forms, data can be bound to user interface controls if it's supplied in collection containers. There might be circumstances in which showing the contents of an enum through a data-bound control is desirable. This is the case, for example, when you're building middle-tier objects and need to strongly identify a particular category of values whose natural type—be it Integer, Byte, or Long—is too generic for the role the data plays in the context of the application. Enumerations make code easier to read and write and easier to understand and debug. A named constant is far more readable than a number. In addition, enums promote strongly typed programming. If the parameter of a method is defined as being of type Categories, for instance, you have to either pass a value from Categories or explicitly throw away some type safety by casting a valid integral value to the Categories type before supplying it to the method.

A set of options and settings for a number of components can be effectively expressed using enum values. Where users can configure the behavior of these components you might want to show options through data-bound controls such as a dropdown list or a group of checkboxes.

The following ASP.NET code shows how to bind the names of the values in an enum to a dropdown list:

DropDownList1.DataSource = _ [Enum].GetNames(GetType(Categories)) DropDownList1.DataBind()

The dropdown list displays the names of the elements and returns the same names when users make their selection. Take a look at the following code:

Response.Write(DropDownList1.SelectedValue)

If the dropdown list is bound to an enum type (see Figure 4), the preceding code fragment returns the displayed string regardless of the value of each entry. It would be nice to have the ability to display symbolic names in a data-bound control and return real numeric values when a selection is made. For this to happen, some changes must be made on the control to specialize it for an enum.

Figure 4 Enum in Dropdown

Figure 4** Enum in Dropdown **

To map a symbolic name to an enumerated value, you use the Parse method, as in the following:

Dim c As Categories = _ [Enum].Parse(GetType(Categories), "Mentoring")

To bind the contents of an enum type to a control, you need to transform those contents into a collection which is then bound. Likewise, to retrieve a selection from the same control, you need an additional parse step. Both actions can be incorporated into a new control. Let's see how to specialize a data-bound control (a dropdown list, for example) to work with enums declaratively.

A Custom Dropdown List Control

Figure 5 lists the code for a new dropdown list control that is built to work with enums. The idea is that the new control takes an enum type name and automatically sets the data source of the control to the contents of the enum type. The new control introduces two new properties to express the type of the bound enum: DataSourceType and DataSourceTypeName. The two properties refer to the same piece of information—the enum type name—in two distinct forms—object and string.

Figure 5 EnumDropDownList

Namespace MsdnMag.CuttingEdge Public Class EnumDropDownList : Inherits DropDownList Public Property DataSourceType() As Type Get Dim o As Object = ViewState("DataSourceType") If o Is Nothing Then Return Type.Missing Else Return DirectCast(o, Type) End If End Get Set(ByVal value As Type) ViewState("DataSourceType") = value If value.BaseType Is GetType(System.Enum) Then DataSource = [Enum].GetNames(value) End If End Set End Property Public Property DataSourceTypeName() As String Get Dim o As Object = ViewState("DataSourceTypeName") If o Is Nothing Then Return String.Empty Else Return DirectCast(o, String) End If End Get Set(ByVal value As String) ViewState("DataSourceTypeName") = value Dim t As Type = Type.GetType(value) If t.BaseType Is GetType(System.Enum) Then DataSource = [Enum].GetNames(t) End If End Set End Property Public ReadOnly Property SelectedEnumValue() As Object Get Dim t As Type If String.IsNullOrEmpty(DataSourceTypeName) Then t = DataSourceType Else t = Type.GetType(DataSourceTypeName) End If If t Is Nothing Then Return Nothing End If If t.BaseType Is GetType(System.Enum) Then Dim o As Object = [Enum].Parse(t, SelectedValue) Return [Enum].ToObject(t, o) Else Return Nothing End If End Get End Property End Class End Namespace

Let's examine the set method of the DataSourceTypeName string property:

Set(ByVal value As String) ViewState("DataSourceTypeName") = value Dim t As Type = Type.GetType(value) If t.BaseType Is GetType(System.Enum) Then DataSource = [Enum].GetNames(t) End If End Set

If the Type object that corresponds to the specified string derives from System.Enum, the DataSource property of the control is automatically set to the array returned by the GetNames method. The DataBind method is not invoked, so you still need to trigger the data-binding operation programmatically when it is more convenient to your code.

As the code demonstrates, by associating a type name (or an equivalent Type object) with the dropdown control you are laying the groundwork for flowing the named constants of the enum into the control via binding. To illustrate the retrieving process, let's examine the code for the SelectedEnumValue property by taking a look at Figure 6.

Figure 6 SelectedEnumValue Property

Public ReadOnly Property SelectedEnumValue() As Object Get Dim t As Type If String.IsNullOrEmpty(DataSourceTypeName) Then t = DataSourceType Else t = Type.GetType(DataSourceTypeName) End If If t Is Nothing Then Return Nothing If t.BaseType Is GetType(System.Enum) Then Dim o As Object = [Enum].Parse(t, SelectedValue) Return [Enum].ToObject(t, o) Else Return Nothing End If End Get End Property

The get accessor of the property SelectedEnumValue is a read-only property. First it ensures that either DataSourceType or DataSourceTypeName is defined and can provide type information. Next, it parses the string returned by the SelectedValue property of the base dropdown list class and obtains an enumerated value. Finally, the value returned by the property is the result of the ToObject method on the enumerated value.

Putting it all together, with the new dropdown list control in Figure 5 you can write ASP.NET pages like so:

<%@ Register Namespace="MsdnMag.CuttingEdge" TagPrefix="msdn" %> ... <msdn:EnumDropDownList ID="DropDownList1" runat="server" AutoPostBack="true" DataSourceTypeName="Categories"> </x:EnumDropDownList>

The SelectedIndexChanged event on the dropdown list can be used to capture the value of the new SelectedEnumValue property, as shown here:

Protected Sub DropDownList1_SelectedIndexChanged( _ ByVal sender As Object, ByVal e As EventArgs) _ Handles DropDownList1.SelectedIndexChanged Response.Write(DirectCast(DropDownList1.SelectedEnumValue, Int32)) End Sub

The sample EnumDropDownList control provides the same capabilities as the parent dropdown plus the ability to bind directly to enums. A similar mechanism can be easily built for Windows Forms applications extending the combobox control.

Formatting Enum Values

The ToObject method takes type information and a symbolic constant and returns the corresponding value. The Format method does something similar except that it converts the specified constant of the enum into an equivalent string or numeric representation:

Dim cat As Categories = Categories.Training Response.Write([Enum].Format(GetType(Categories), cat, outputFormat))

The Format method supports a few predefined output formats, each identified with a letter. The outputFormat variable in the preceding code snippet can take any of the following values: F, D, X, or G. The X format represents the value as a hexadecimal value without the leading 0x. The D format renders the value as a decimal value. The F format toggles between the symbolic and numeric representation of an enum value. For example, if you call Format and specify the value as, say, Training, it will return the numeric value of the element. If you call Format on a numeric value, the output string contains the symbolic name. The G format works like F except that it also fully supports enum types with the Flags attribute.

The Flags Attribute

The Flags attribute indicates that the elements of an enumeration can be composed in a bitwise manner:

<Flags> _ Public Enum Categories As Int32 Speaking = 1 Training = 2 Consulting = 4 Mentoring = 8 All = Speaking Or Training Or Consulting Or Mentoring End Enum

When the Flags attribute is used, you can use the Or operator in Visual Basic (and the | operator in C#) to combine values to form new valid values of the same enum type. In this case, it is essential that you give explicit values to enum values as unique powers of two. It is interesting to note that when Flags is specified, Parse and Format methods feature advanced capabilities. For example, the following code returns a comma-separated string of constants: "Speaking, Training, Consulting".

Response.Write([Enum].Format(GetType(Categories), 7, "g"))

Likewise, the Parse method can successfully parse a comma-separated string like the one just shown into the proper numeric value. This is a very powerful feature, especially when you're reading options from the configuration file of an application:

<appSettings> <add key="Categories" value="Speaking, Training" /> </appSettings>

You use the ConfigurationManager class to retrieve the string from the configuration file and then convert it into the format that is more convenient:

Dim o As Object = [Enum].Parse(GetType(MyCategories), _ ConfigurationManager.AppSettings("Categories")) Dim n As Int32 = DirectCast(o, Int32) Response.Write(n)

The preceding code reads back the configuration section and converts to an integer.

Enum Values in a Data Access Layer

Now that I've recapped the basics of enum types and investigated some of the internals, I'd like to turn to the set of questions asked by one of my readers. The reader built a relatively simple data access layer; for the most part, he relied on table adapters. However, he extended the set of classes generated by the Visual Studio® 2005 data designer with some helper classes to better describe some of the logical entities and relationships in the problem domain. One of the entities—I'll call it the Document—required a member describing the source of the contained data. In the example, the member can take a value from a short list of fixed values. The reader defined an enum like this:

Public Enum SourceType As Short Unknown = -1 Xml = 1 SqlDatabase JetDatabase RawText End Enum

He wrote ADO.NET code to save and restore these values to a SQL Server™ database and was looking for an effective way to list available options from within ASP.NET pages and Windows Forms. The GetValues and GetNames methods, along with the Parse method, were obvious choices. I suggested the creation of a couple of custom controls (like the aforementioned EnumDropDownList) to make the writing of the user interface simpler. All in all, is using an enum in a similar situation a good choice?

I see two main disadvantages to using enum types: maintainability and localization. Incorporating the values of the enum types into the user interface was child's play for the reader and made the involved component neater, easier to read, even more elegant, and extremely cheap to implement. The rub here is that an enum type bound to UI controls exposes internal information—specifically, type information—to the presentation layer. A dropdown list like the one in Figure 4 shows off type information. If you modify the type by renaming one of the members, for example, you have to recompile the assembly in which the type is defined.

As an alternative you could create an entity class for the information and supply a data provider component that wraps the behavior of the entity. This provider component would take care of exposing the contents of the entity through a set of contracted methods. Where does the provider get its data? Typically, entity information is obtained from database tables. Changes to the information involve the database administrator and don't require a compile step. In the database table, you could provide for an ID and DisplayName pair of columns for a neat separation between UI, presentation data, and type information, and still maintain a strongly typed programming style. If you're familiar with the Northwind database, you know that it features a Categories and Products table. Each product is bound to a category via a classic foreign-key relationship. Admittedly, there's a bit of abstraction in this solution that many real programmers tend to skip as an unneeded layer of complexity. The reader decided that in his own solution with a lot of table adapters and DataSet, a data-mapper layer would have been too much even if limited to a couple of entities. So he opted for enum types and actually delivered the application.

A few months later, his boss asked him to provide a localized version of the application. How can you localize an enum type? Well, normally you won't need to. Enum types are numeric values and numbers look the same in many cultures. But this is not the case if you use the symbolic name of the constant in the user interface. In that case, you'd have to redefine the type!

To make a long story short, a choice made to simplify and speed up coding proved later to be patently wrong. There is a lesson to be learned here: enum types are a powerful tool, but they are not appropriate for every situation.

Send your questions and comments for Dino to cutting@microsoft.com.

Dino Esposito is a mentor at Solid Quality Learning and the author of Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Based in Italy, Dino is a frequent speaker at industry events worldwide. Get in touch with Dino atcutting@microsoft.com or join the blog at weblogs.asp.net/despos.