An enumeration, or enum, is a programmer-defined type, such as a class or a struct.
- Like structs, enums are value types and therefore store their data directly, rather than separately, with a reference and data.
- Enums have only one type of member: named constants with integer values.
The following code shows an example of the declaration of a new enum type called TrafficLight
, which contains three members. Notice that the list of member declarations is a comma-separated list; there are no semicolons in an enum declaration.
Keyword Enum name
↓ ↓
enum TrafficLight
{
Green, ← Comma separated—no semicolons
Yellow, ← Comma separated—no semicolons
Red
}
Every enum type has an underlying integer type, which by default is int
.
- Each enum member is assigned a constant value of the underlying type.
- By default, the compiler assigns
0
to the first member and assigns each subsequent member the value one more than the previous member.
For example, in the TrafficLight
type, the compiler assigns the int
values 0
, 1
, and 2
to members Green
, Yellow
, and Red
, respectively. In the output of the following code, you can see the underlying member values by casting them to type int
. Figure 11-1 illustrates their arrangement on the stack.
TrafficLight t1 = TrafficLight.Green;
TrafficLight t2 = TrafficLight.Yellow;
TrafficLight t3 = TrafficLight.Red;
Console.WriteLine("{0}, {1}", t1, (int) t1);
Console.WriteLine("{0}, {1}", t2, (int) t2);
Console.WriteLine("{0}, {1}
", t3, (int) t3);
↑
Cast to int
This code produces the following output:
Green, 0
Yellow, 1
Red, 2
You can assign enum values to variables of the enum type. For example, the following code shows the declaration of three variables of type TrafficLight
. Notice that you can assign member literals to variables, or you can copy the value from another variable of the same type.
class Program
{
static void Main()
{
Type Variable Member
↓ ↓ ↓
TrafficLight t1 = TrafficLight.Red; // Assign from member
TrafficLight t2 = TrafficLight.Green; // Assign from member
TrafficLight t3 = t2; // Assign from variable
Console.WriteLine(t1);
Console.WriteLine(t2);
Console.WriteLine(t3);
}
}
This code produces the following output. Notice that the member names are printed as strings.
Red
Green
Green
You can use an integer type other than int
by placing a colon and the type name after the enum name. The type can be any integer type. All the member constants are of the enum’s underlying type.
Colon
↓
enum TrafficLight : ulong
{ ↑
... Underlying type
The values of the member constants can be any values of the underlying type. To explicitly set the value of a member, use an initializer after its name in the enum declaration. There can be duplicate values, although not duplicate names, as shown here:
enum TrafficLight
{
Green = 10,
Yellow = 15, // Duplicate values
Red = 15 // Duplicate values
}
For example, the code in Figure 11-2 shows two equivalent declarations of enum TrafficLight
.
- The code on the left accepts the default type and numbering.
- The code on the right explicitly sets the underlying type to
int
and the members to values corresponding to the default values.
You can explicitly assign the values for any of the member constants. If you don’t initialize a member constant, the compiler implicitly assigns it a value. Figure 11-3 illustrates the rules the compiler uses for assigning those values.
- The values associated with the member names do not need to be distinct.
For example, the following code declares two enumerations. CardSuit
accepts the implicit numbering of the members, as shown in the comments. FaceCards
sets some members explicitly and accepts implicit numbering of the others.
enum CardSuit
{
Hearts, // 0 - Since this is first
Clubs, // 1 - One more than the previous one
Diamonds, // 2 - One more than the previous one
Spades, // 3 - One more than the previous one
MaxSuits // 4 - A common way to assign a constant
} // to the number of listed items
enum FaceCards
{
// Member // Value assigned
Jack = 11, // 11 - Explicitly set
Queen, // 12 - One more than the previous one
King, // 13 - One more than the previous one
Ace, // 14 - One more than the previous one
NumberOfFaceCards = 4, // 4 - Explicitly set
SomeOtherValue, // 5 - One more than the previous one
HighestFaceCard = Ace // 14 - Ace is defined above
}
Programmers have long used the different bits in a single word as a compact way of representing a set of on/off flags. In this section I’ll call this word the flag word. Enums offer a convenient way to implement this.
The general steps are the following:
- Determine how many bit flags you need and choose an unsigned integer type with enough bits to hold them.
- Determine what each bit position represents and give it a name. Declare an enum of the chosen integer type, with each member represented by a bit position.
- Use the bitwise OR operator to set the appropriate bits in a word holding the bit flags.
- You can then check whether a particular bit flag is set by using either the
HasFlag
method or the bitwise AND operator.
For example, the following code shows the enum declaration representing the options for a card deck in a card game. The underlying type, uint
, is more than sufficient to hold the four bit flags needed. Notice the following about the code:
- The members have names that represent binary options.
- Each option is represented by a particular bit position in the word. Bit positions hold either a 0 or a 1.
- Since a bit flag represents a set of bits that are either on or off, you do not want to use 0 as a member value. It already has a meaning—that all the bit flags are off.
- With hexadecimal representation, every hexadecimal digit represents exactly four bits. Because of this direct correlation between the bit patterns and hexadecimal representation, when working with bit patterns, hex is often used rather than decimal representation.
- Decorating the enum with the
Flags
attribute is not actually necessary, but gives some additional convenience, which I’ll discuss shortly. An attribute appears as a string between square brackets, immediately preceding a language construct. In this case, the attribute is immediately before the enum declaration. I’ll cover attributes in Chapter 24.
[Flags]
enum CardDeckSettings : uint
{
SingleDeck = 0x01, // Bit 0
LargePictures = 0x02, // Bit 1
FancyNumbers = 0x04, // Bit 2
Animation = 0x08 // Bit 3
}
Figure 11-4 illustrates this enumeration.
To create a word with the appropriate bit flags, declare a variable of the enum type and use the bitwise OR operator to set the required bits. For example, the following code sets three of the four options in the flag word:
Enum type Flag word Bit flags ORed together
↓ ↓ ↓
CardDeckSettings ops = CardDeckSettings.SingleDeck
| CardDeckSettings.FancyNumbers
| CardDeckSettings.Animation ;
To check whether the flag word has a particular bit flag set, you can use the Boolean HasFlag
method of the enum type. You call the HasFlag
method on the flag word, passing in the bit flag you’re checking for, as shown in the following line of code. If the specified bit flag is set, HasFlag
returns true
; otherwise, it returns false
.
bool useFancyNumbers = ops.HasFlag(CardDeckSettings.FancyNumbers);
↑ ↑
Flag word Bit flag
The HasFlag
method can also check for multiple bit flags. For example, the following code checks whether the flag word, ops
, has both the Animation
and FancyNumbers
bits set. The code does the following:
- The first statement creates a test word instance, called
testFlags
, with theAnimation
andFancyNumbers
bits set.- It then passes
testFlags
as the parameter to theHasFlag
method.HasFlags
checks whether all the flags that are set in the test word are also set in the flag word,ops
. If they are, thenHasFlag
returnstrue
. Otherwise, it returnsfalse
.
CardDeckSettings testFlags =
CardDeckSettings.Animation | CardDeckSettings.FancyNumbers;
bool useAnimationAndFancyNumbers = ops.HasFlag( testFlags );
↑ ↑
Flag word Test word
Another method of determining whether one or more particular bits is set is to use the bitwise AND operator. For example, like the code above, the following code checks a flag word to see whether the FancyNumbers
bit flag is set. It does this by ANDing the flag word with the bit flag and then comparing that result with the bit flag. If the bit was set in the original flag word, then the result of the AND operation will have the same bit pattern as the bit flag.
bool useFancyNumbers =
(ops & CardDeckSettings.FancyNumbers) == CardDeckSettings.FancyNumbers;
↑ ↑
Flag word Bit flag
Figure 11-5 illustrates the process of creating the flag word and then using the bitwise AND operation to determine whether a particular bit is set.
The preceding code used the Flags
attribute just before declaring the enum, as copied here:
[Flags]
enum CardDeckSettings : uint
{
...
}
The Flags
attribute does not change the calculations at all. It does, however, provide several convenient features. First, it informs the compiler, object browsers, and other tools looking at the code that the members of the enum are meant to be combined as bit flags, rather than used only as separate values. This allows the browsers to interpret variables of the enum type more appropriately.
Second, it allows the ToString
method of an enum to provide more appropriate formatting for the values of bit flags. The ToString
method takes an enum value and compares it to the values of the constant members of the enum. If it matches one of the members, ToString
returns the string name of the member.
Examine, for example, the following code, where the enum is not prefaced by the Flags
attribute.
enum CardDeckSettings : uint
{
SingleDeck = 0x01, // bit 0
LargePictures = 0x02, // bit 1
FancyNumbers = 0x04, // bit 2
Animation = 0x08 // bit 3
}
class Program
{
static void Main( )
{
CardDeckSettings ops;
ops = CardDeckSettings.FancyNumbers; // Set one flag.
Console.WriteLine( ops.ToString() );
// Set two bit flags.
ops = CardDeckSettings.FancyNumbers | CardDeckSettings.Animation;
Console.WriteLine( ops.ToString() ); // Print what?
}
}
This code produces the following output:
FancyNumbers
12
In this code, method Main
does the following:
- Creates a variable of the enum type
CardDeckSettings
, sets one of its bit flags, and prints out the value of the variable—which is the valueFancyNumbers
- Assigns the variable a new value, which consists of two of the bit flags being set, and prints out its value—which is 12
The value 12
, displayed as the result of the second assignment, is the value of ops
, as an int
, since FancyNumbers
sets the bit for the value 4
, and Animation
sets the bit for the value 8
—giving an int
value of 12
. In the WriteLine
method following the assignment, when the ToString
method tries to look up the name of the enum member with a value of 12
, it finds that there is no member with that value—so it just prints out the value.
If, however, we were to add back the Flags
attribute before the declaration of the enum, this would tell the ToString
method that the bits can be considered separately. In looking up the value, ToString
would then find that 12
corresponds to two separate bit flag members—FancyNumbers
and Animation
—and would return the string containing their names, separated by a comma and a space. The following shows the result of running the code again, with the Flags
attribute:
FancyNumbers
FancyNumbers, Animation
The following code puts together all the pieces of using bit flags:
[Flags]
enum CardDeckSettings : uint
{
SingleDeck = 0x01, // bit 0
LargePictures = 0x02, // bit 1
FancyNumbers = 0x04, // bit 2
Animation = 0x08 // bit 3
}
class MyClass
{
bool UseSingleDeck = false,
UseBigPics = false,
UseFancyNumbers = false,
UseAnimation = false,
UseAnimationAndFancyNumbers = false;
public void SetOptions( CardDeckSettings ops )
{
UseSingleDeck = ops.HasFlag( CardDeckSettings.SingleDeck );
UseBigPics = ops.HasFlag( CardDeckSettings.LargePictures );
UseFancyNumbers = ops.HasFlag( CardDeckSettings.FancyNumbers );
UseAnimation = ops.HasFlag( CardDeckSettings.Animation );
CardDeckSettings testFlags =
CardDeckSettings.Animation | CardDeckSettings.FancyNumbers;
UseAnimationAndFancyNumbers = ops.HasFlag( testFlags );
}
public void PrintOptions( )
{
Console.WriteLine( "Option settings:" );
Console.WriteLine( " Use Single Deck - {0}", UseSingleDeck );
Console.WriteLine( " Use Large Pictures - {0}", UseBigPics );
Console.WriteLine( " Use Fancy Numbers - {0}", UseFancyNumbers );
Console.WriteLine( " Show Animation - {0}", UseAnimation );
Console.WriteLine( " Show Animation and FancyNumbers - {0}",
UseAnimationAndFancyNumbers );
}
}
class Program
{
static void Main( )
{
MyClass mc = new MyClass( );
CardDeckSettings ops = CardDeckSettings.SingleDeck
| CardDeckSettings.FancyNumbers
| CardDeckSettings.Animation;
mc.SetOptions( ops );
mc.PrintOptions( );
}
}
This code produces the following output:
Option settings:
Use Single Deck - True
Use Large Pictures - False
Use Fancy Numbers - True
Show Animation - True
Show Animation and FancyNumbers - True
Enums have only a single member type: the declared member constants.
- You cannot use modifiers with the members. They all implicitly have the same accessibility as the enum.
- The members are
static
, which, as you’ll recall, means that they are accessible even if there are no variables of the enum type. As with all statics, use the type name, followed by a dot and the member name, to use a member.
For example, the following code does not create any variables of the enum TrafficLight
type, but because the members are static, they are accessible and can be printed using WriteLine
.
static void Main()
{
Console.WriteLine("{0}", TrafficLight.Green);
Console.WriteLine("{0}", TrafficLight.Yellow);
Console.WriteLine("{0}", TrafficLight.Red);
}
↑ ↑
Enum name Member name
An enum is a distinct type. Comparing enum members of different enum types results in a compile-time error. For example, the following code declares two different enum types with the exact same structure and member names.
- The first
if
statement is fine because it compares different members from the same enum type.- The second
if
statement produces an error because it attempts to compare members from different enum types. This error occurs even though the structures and member names are exactly the same.
enum FirstEnum // First enum type
{
Mem1,
Mem2
}
enum SecondEnum // Second enum type
{
Mem1,
Mem2
}
class Program
{
static void Main()
{
if (FirstEnum.Mem1 < FirstEnum.Mem2) // OK--members of same enum type
Console.WriteLine("True");
if (FirstEnum.Mem1 < SecondEnum.Mem1) // Error--different enum types
Console.WriteLine("True");
}
}
There are also several useful static
methods of the .NET Enum
type, on which enum
is based:
- The
GetName
method takes an enum type object and an integer, and returns the name of the corresponding enum member.- The
GetNames
method takes an enum type object and returns all the names of all the members in the enum.
The following code shows an example of each of these methods being used. Notice that you have to use the typeof
operator to get the enum type object.
enum TrafficLight
{
Green,
Yellow,
Red
}
class Program
{
static void Main()
{
Console.WriteLine( "Second member of TrafficLight is {0}
",
Enum.GetName( typeof( TrafficLight ), 1 ) );
foreach ( var name in Enum.GetNames( typeof( TrafficLight ) ) )
Console.WriteLine( name );
}
}
This code produces the following output:
Second member of TrafficLight is Yellow
Green
Yellow
Red
3.133.123.34