Chapter 13. Enumerations

Enumerations

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.

Enumerations

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.

Enumerations

This code produces the following output:

Green,  0
Yellow, 1
Red,    2
The member constants of an enum are represented by underlying integral values.

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.

The member constants of an enum are represented by underlying integral values.

This code produces the following output. Notice that the member names are printed as strings.

Red
Green
Green

Setting the Underlying Type and Explicit Values

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.

Setting the Underlying Type and Explicit Values

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.

Equivalent enum declarations

Figure 13-2. Equivalent enum declarations

Implicit Member Numbering

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.

The algorithm for assigning member 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
    }

Bit Flags

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:

  1. Determine how many bit flags you need, and choose an unsigned integral type with enough bits to hold them.

  2. 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.

  3. Use the bitwise OR operator to set the appropriate bits in a word holding the bit flags.

  4. 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.

Definition of the flag bits, and their individual representations

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:

Definition of the flag bits, and their individual representations

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.

Definition of the flag bits, and their individual representations

Figure 13-5 illustrates the process of creating the flag word and then checking whether a particular bit is set.

Producing a flag word and checking it for a particular bit

Figure 13-5. Producing a flag word and checking it for a particular bit

The Flags Attribute

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

Example Using Bit Flags

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

More About Enums

Enums only have 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.

  • Since the members are constants, 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.

More About Enums

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");
    }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.41.212