An enumeration, or enum, isa programmer-defined type, like a class or a struct.
Like structs, enumsare value types, and therefore store their data directly, rather than separately, with a reference and data.
Enums have only one typeof member: named constants with integral 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.
Every enum type has an underlying integral type, which by default is int
.
Each enum member is assigned a constant value of the underlying type.
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
. Their arrangement on the stack is illustrated in Figure 13-1.
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.
This code produces the following output. Notice that the member names are printed as strings.
Red Green Green
You can use an integral type other than int
by placing a colonand the type name after the enum name. The type can be any integral type except char
. All the member constants are of the enum's 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 13-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. The rules the compiler uses for assigning those values are illustrated in Figure 13-3.
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. 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 integral type with enough bits to hold them.
Determine what each bit position represents, and give it a name. Declare an enum of the chosen integral 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.
Unpack the bit flags by using 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 bit that is 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.
Hexadecimal representation is often used when working with bit patterns because there is a more direct correlation between a bit pattern and its hexadecimal representation than with its decimal representation.
Decorating the enum with the Flags
attribute is not actually necessary, but gives some additional convenience, which I will discuss shortly. Attributes are covered in Chapter 24.
[Flags] enum CardDeckSettings : uint { SingleDeck = 0x01, // Bit 0 LargePictures = 0x02, // Bit 1 FancyNumbers = 0x04, // Bit 2 Animation = 0x08 // Bit 3 }
Figure 13-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:
To determine whether a particular bit is set, use the bitwise AND operator with the flag word and the bit flag.
For example, the following code checks a value to see whether the FancyNumbers
bit flag is set. It does this by ANDing that value with the bit flag, and then comparing that result with the bit flag. If the bit was set in the original value, then the result of the AND operation will have the same bit pattern as the bit flag.
Figure 13-5 illustrates the process of creating the flag word and then checking whether a particular bit is set.
We'll cover attributes in Chapter 24, but it's worth mentioning the Flags
attribute here. An attribute appears as a string between square brackets placed on the line above a class declaration. The 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 allowsthe 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.
Suppose, for example, that you have used the enum declaration for CardDeckSettings
(given in the preceding code), and have not used the Flags
attribute. The first line of the following code creates a variable (named ops
) of the enum type, and sets the value of a single flag bit. The second line uses ToString
to get the string name of the member represented by that value.
CardDeckSettings ops = CardDeckSettings.FancyNumbers; // Set the bit flag. Console.WriteLine( ops.ToString() ); // Print its name.
This code produces the following output:
FancyNumbers
That's all well and good, but suppose you set two bit flags instead of one, as in the following code:
// Set two bit flags. ops = CardDeckSettings.FancyNumbers | CardDeckSettings.Animation; Console.WriteLine( ops.ToString() ); // Print what?
The resulting value of ops
is 12, where 4 is from the FancyNumbers
flag, and 8 is from the Animation
flag. In the second line, when ToString
attempts to look up the value in the list of enum members, it finds that there is no member with the value 12—so it just returns the string representing 12. The resulting output is the following:
12
If, however, you use the Flags
attribute before the declaration of the enum, this tells the ToString
method that the bits can be considered separately. In looking up the value, it would find that 12 corresponds to the two bit flag members FancyNumbers
and Animation
. It would then return the string containing their names, separated by a comma and space, as shown here:
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; bool UseBigPics = false; bool UseFancyNums = false; bool UseAnimation = false; public void SetOptions(CardDeckSettings ops) { UseSingleDeck = (ops & CardDeckSettings.SingleDeck) == CardDeckSettings.SingleDeck; UseBigPics = (ops & CardDeckSettings.LargePictures) == CardDeckSettings.LargePictures; UseFancyNums = (ops & CardDeckSettings.FancyNumbers) == CardDeckSettings.FancyNumbers; UseAnimation = (ops & CardDeckSettings.Animation) == CardDeckSettings.Animation; } 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}", UseFancyNums); Console.WriteLine(" Show Animation - {0}", UseAnimation); } }
class Program { static void Main() { MyClass mc = new MyClass(); CardDeckSettings ops = CardDeckSettings.SingleDeck | CardDeckSettings.FancyNumbers | CardDeckSettings.Animation; mc.SetOptions(ops); mc.PrintOptions(); } }
This code producesthe following output:
Option settings: Use Single Deck - True Use Large Pictures - False Use Fancy Numbers - True Show Animation - True
Enums only have a single member type: the declared member constants.
For example, the following code does not create any variables of the enum TrafficLight
type, but the members are accessible, and can be printed using WriteLine
.
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 enum types.
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 compares members from different enum types, even though their 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"); } }
3.22.41.212