An enumeration, or enum, is a programmer-defined type, like a class or a struct.
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
.
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 13-1 illustrates their arrangement on the stack.
This code produces the following output:
Green, 0
Yellow, 1
Red, 2
Figure 13-1. The member constants of an enum are represented by underlying integral values.
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 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.
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
.
int
and the members to values corresponding to the default values.Figure 13-2. Equivalent enum declarations
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 13-3 illustrates the rules the compiler uses for assigning those values.
Figure 13-3. The algorithm for assigning member values
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:
HasFlag
method.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:
Flags
attribute is not actually necessary but gives some additional convenience, which I'll 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.
Figure 13-4. Definition of the flag bits, and their individual representations
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:
Prior to C# 4.0, to determine whether a particular bit was set, you would 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.
Figure 13-5. Producing a flag word and checking it for a particular bit flag
This process of checking a flag word for a particular bit or set of bits is such a common task that C# 4.0 introduced a new instance method to the enum type to do the process for you. The method is called HasFlag
. You use it on an instance of a flag word and pass it the bit flag you want to check for.
For example, the previous check for useFancyNumbers
can be significantly shortened and simplified to the following statement:
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:
testFlags
, with the Animation
and FancyNumbers
bits set.testFlags
as the parameter to the HasFlags
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, then HasFlag
returns true
. Otherwise, it returns false
.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 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.
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. Suppose also that you didn't use the Flags
attribute on the enum declaration.
// 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 change your code to 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,
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( string[] args )
{
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.
static
, they are accessible even if there are no variables of the enum type. Use the enum type name, followed by a dot and the member name.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.
if
statement is fine because it compares different members from the same enum type.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.12.108.175