C H A P T E R  11

Enumerations

Enumerations

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

Image

Figure 11-1. The member constants of an enum are represented by underlying integer 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.

   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

Setting the Underlying Type and Explicit Values

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

Figure 11-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. 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.
Image

Figure 11-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. 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:

  1. Determine how many bit flags you need and choose an unsigned integer 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 integer 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. 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.

Image

Figure 11-4. Definition of the flag bits (left), along with their individual representations (right)

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 the Animation and FancyNumbers bits set.
  • It then passes testFlags as the parameter to the HasFlag 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.
   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.

Image

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

The Flags Attribute

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 value FancyNumbers
  • 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

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,
           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 );
      }
   }
                                                                                         Image
   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

More About Enums

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

..................Content has been hidden....................

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