Click here to Skip to main content
15,860,844 members
Articles / Programming Languages / C#

Using Enumerations: Enumerated Types and Bit Flags

Rate me:
Please Sign up or sign in to vote.
4.12/5 (10 votes)
22 Jul 2009CPOL5 min read 29.4K   22   2
An article that explains how to use Enumerations

Introduction

The Common Language Runtime and the Framework Class Library work together to make enumerated types and bit flags powerful object-oriented types that offer many unrecognized features to .NET developers. An enumeration is a special type that maps a set of names to numeric values. Using them is an alternative to embedding constants in your code, and provides a higher level of nominal type safety. Enumerated types look just like ordinary types in metadata, although they abide by a strict set of rules as defined on the Common Type Specification (CTS). An enum type itself derives from System.Enum, which itself derives from System.ValueType. Each is backed by a specific primitive data type, one of Boolean, Char, Byte, Int16, Int32, Int64, SByte, UInt16, UInt32, IntPtr, Single and Double. Int32 is used as the default in most cases. However, unlike other value types, an enumerated type cannot define any methods, properties, or events. An instance of a single enum contains a single field representing its value. And because enums are value types, having an instance of one is essentially the same as having a value of its backing store type, except that you can refer to it by type name. In C#, you simply write the following to create a new enum:

C#
enum  Color :  byte
{
    Red,
    Green,
    Blue
}

The part specifying the enum’s type is optional. The C# compiler handles the necessary translation into metadata, which follows the above rules:

C#
.class private auto ansi sealed Color
       extends [mscorlib]System.Enum
{
  .field public specialname rtspecialname uint8 value__
  .field public static literal valuetype Color Red = uint8(0x00)
  .field public static literal valuetype Color Green = uint8(0x01)
  .field public static literal valuetype Color Blue = uint8(0x02)

The Color enum could then be used in a program that prompts a user to enter his favorite color. Use the enum to present the list, parse the input, and then pass around the selection to various routines:

C#
using System; 
   enum Color : byte
   {
     Red,
     Green,
     Blue
    }

    class ColorPicker
        {
            internal static void Main()
            {
                Color favorite = SelectFavoriteColor();
                RespondToFavoriteColor(favorite);
            }

            static Color SelectFavoriteColor()
            {
                Color favorite = (Color)(0xFF);

                // Loop until a valid color has been selected.
                do
                {
                    // Display the prompt and a list of valid colors.
                    Console.Write("Please enter your favorite color (");
                    foreach (string name in Enum.GetNames(typeof(Color)))
                        Console.Write(" {0} ", name);
                    Console.Write("): ");

                    string input = Console.In.ReadLine();
                    try
                    {
                        favorite = (Color)Enum.Parse(typeof(Color), input, true);
                    }
                    catch (ArgumentException)
                    {
                        // User's input didn't match an enum name.
                        Console.WriteLine("Bad input, please choose again!");
                        Console.WriteLine();
                    }
                }
                while (favorite == (Color)(0xFF));

                return favorite;
            }

            static void RespondToFavoriteColor(Color c)
            {
                // Notice that C# enables you to switch over an enum.
                switch (c)
                {
                    case Color.Red:
                        // Do something for people that like `Red'.
                        Console.WriteLine("you like red");
                        break;
                    case Color.Green:
                        // Do something for people that like `Green'.
                        Console.WriteLine("you like green");
                        break;
                    case Color.Blue:
                        // Do something for people that like `Blue'.
                        Console.WriteLine("you like blue");
                        break;
                    default:
                        // An unrecognized color got passed in.
                        // This is probably an error! But we need to handle it.
                        Console.WriteLine("error!");
                        break;
                }
            }
 }

Here is the output:

Please enter your favorite color ( Red  Green  Blue ): Black
Bad input, please choose again!

Please enter your favorite color ( Red  Green  Blue ): Green
you like green

Enumerated Types: A Closer Look

As we have seen above, the compiler translates the code to emit metadata that is zero based. Because an enum is a special type that maps a name to an integer, an enumerated type is a type that defines a set of symbolic names and value pairs. So when we write:

C#
internal enum Color {
White,        	// assigned the value of 0 
Red,           	// assigned the value of 1
Green,      	// assigned the value of 2
Blue,         	// assigned the value of 3
Purple      	// assigned the value of 4
}

Rather than writing code that uses 0 to represent white, 1 to represent red, .NET developers shouldn't hard-code numbers into their code; they should use an enumerated type. A look at the metadata reveals why. So basically, an enumerated type is just a structure with a bunch of constant fields defined in it and one instance field. The constant fields are emitted to the assembly’s metadata and can be accessed via reflection. This means that you can get all of the symbols and their values associated with an enumerated type at runtime. Here is a code example that uses the Enum type’s static GetUnderlyingType:

C#
using System;

public enum Color /* : byte */ {
   White,       // Assigned a value of 0
   Red,         // Assigned a value of 1
   Green,       // Assigned a value of 2
   Blue,        // Assigned a value of 3
   Orange,      // Assigned a value of 4
}

public static class Program {
   public static void Main() {
      Console.WriteLine(Enum.GetUnderlyingType(typeof(Color)));

      // Create a variable of type Color and initialize it to Blue
      Color c = Color.Blue;
      Console.WriteLine(c);               // "Blue" (General format)
      Console.WriteLine(c.ToString());    // "Blue" (General format)
      Console.WriteLine(c.ToString("G")); // "Blue" (General format)
      Console.WriteLine(c.ToString("D")); // "3"    (Decimal format)
      Console.WriteLine(c.ToString("X")); // "03"   (Hex format)

      // The following line displays "Blue".
      Console.WriteLine(Enum.Format(typeof(Color), 3, "G"));

      // Returns array of Color enums
      Color[] colors = (Color[]) Enum.GetValues(typeof(Color));
      Console.WriteLine("Number of symbols defined: " + colors.Length);
      Console.WriteLine("Value\tSymbol\n-----\t------");
      foreach (Color color in colors) {
         // Display each symbol in Decimal and General format.
         Console.WriteLine("{0,5:D}\t{0:G}", color);
      }

      // Because Orange is defined as 4, 'c' is initialized to 4.
      c = (Color) Enum.Parse(typeof(Color), "orange", true);

      // Because Brown isn’t defined, an ArgumentException is thrown.
      try {
         c = (Color) Enum.Parse(typeof(Color), "Brown", false);
      }
      catch (ArgumentException) {
         Console.WriteLine("Brown is not defined by the Color enumerated type.");
      }

      // Creates an instance of the Color enum with a value of 1
      c = (Color) Enum.Parse(typeof(Color), "1", false);

      // Creates an instance of the Color enum with a value of 23
      c = (Color) Enum.Parse(typeof(Color), "23", false);

      // Displays "True" because Color defines Red as 1
      Console.WriteLine(Enum.IsDefined(typeof(Color),  1));

      // Displays "True" because Color defines White as 0
      Console.WriteLine(Enum.IsDefined(typeof(Color), "White"));

      // Displays "False" because a case-sensitive check is performed 
      Console.WriteLine(Enum.IsDefined(typeof(Color), "white"));

      // Displays "False" because Color doesn't have a symbol of value 10
      Console.WriteLine(Enum.IsDefined(typeof(Color),  10));

      
      SetColor((Color) 3);  // Blue
      try {
         SetColor((Color) 547);  // Not a defined color
      }
      catch (ArgumentOutOfRangeException e) {
         Console.WriteLine(e);
      }
   }

   public static void SetColor(Color c) {
      if (!Enum.IsDefined(typeof(Color), c)) {
         throw(new ArgumentOutOfRangeException("c", c, "You didn't pass a valid Color"));
      }
      // Set color to Red, Green, Blue, or Orange...
   }
}

The output is:

System.Int32
Blue
Blue
Blue
3
00000003
Blue
Number of symbols defined: 5

   Value     Symbol
--------     ------
     0        White
     1        Red
     2        Green
     3        Blue
     4        Orange

Brown is not defined by the Color enumerated type.
True
True
False
False
System.ArgumentOutOfRangeException: You didn't pass a valid Color
Parameter name: c
Actual value was 547.
   at Program.SetColor(Color c)
   at Program.Main()

Flag-Style Enumerations

A flag is an algorithm that indicates the status of an operation. And sometimes, rather than having the values for an enum exclusive, applications must represent a single instance as a combination of values. An enumerated type may be annotated with the System.FlagsAttribute custom attribute. For example, consider a set of permissions for files. It’s common to open a file for both Read and Write simultaneously. Using a flag-style enum enables you to represent this idea. Notice the numeric value for Read and Write are in the power of two, starting with 1. This is because combining or extracting values from a single instance is done using a bitwise AND or OR. Additional values would occupy 4, 8, 16, 32, and so on. Looking at the code below, 1 | 2 = 3 (that is, 1 bitwise Or 2 = 3):

C#
  using System;
    [Flags]
    enum FileAccess
    {
        Read = 1,
        Write = 2,
        ReadWrite = 3
    }
   class App {
    public static void Main()
    {
        FileAccess rw1 = FileAccess.Read | FileAccess.Write; // value `3'
        Console.WriteLine("rw1 == {0}", rw1);
        FileAccess rw2 = FileAccess.ReadWrite; // value `3'
        Console.WriteLine("rw2 == {0}", rw2);

        Console.WriteLine("rw1 == rw2? {0}", rw1 == rw2);

        if (rw1 == FileAccess.Read)
            Console.WriteLine("try #1: read permitted");
        else
            Console.WriteLine("try #1: read denied");

        if ((rw2 & FileAccess.Read) != 0)
            Console.WriteLine("try #2: read permitted");
        else
            Console.WriteLine("try #2: read denied");
    }
}

The output is:

rw1 == ReadWrite
rw2 == ReadWrite
rw1 == rw2? True
try #1: read denied
try #2: read permitted

The above example is an example of a program that works with bit flags. When defining an enumerated type that can be used to identify bit flags, you should explicitly assign a numeric value to each symbol. Documentation recommends that you apply the System.FlagsAttribute custom attribute type to the enumerated type as shown below:

C#
[Flags]   //  The C# compiler allows either "Flags" or "FlagsAttribute"
internal enum Actions {
None      =      0
Read      =      0x0001,
Write      =     0x0002,
ReadWrite =  Actions.Read |  Actions.Write
Delete   =       0x0004
Query =          0x0008
Sync =           0x0010
}

Because Actions is an enumerated type, you can use all of its methods when working with bit flag enumerated types. For example, assume you had written the following code:

C#
Actions  actions = Actions.Read | Actions.Delete;   	// 0x0005 ( 1 OR 4)
Console.WriteLine(actions.ToString());             	//  "Read", "Delete"

When ToString is called, it attempts to translate the numeric value into its symbolic equivalent, as ToString derives from Object and represents an object as a string. The numeric value is 0x0005, which has no numeric symbolic equivalent. However, the ToString method detects the existence of the [Flags] attribute on the Actions type. ToString now treats the numeric value not as a single value but as a set of flags. Because the 0x0001 and 0x0004 flags are set, ToString generates the following string: “Read”, “Delete”. If you remove the [Flags] attribute from the Actions type, ToString would return “5”. Note that the ToString method offers three ways to format the output: “G” (general), “D” (decimal), and “X” (hex).

A Brief Word on Formatting

When converting numbers into strings using the ToString method, a more extensive default formatting pattern is used:

Specifier     Name            Meaning
---------------------------------------------------------------------------------------
C             Currency        Formats a string into a culture-aware currency attributes
D             Decimal         Formats an integral into a decimal
E             Scientific      Uses scientific engineering notation
F             Fixed Point     Prints a fixed-point format
G             General         General numeric formatting
N             Number          General numeric formatting that uses culture information
P             Percent         Scales a number by 100 and formats it as a percentage
R             Round Trip      Formats a number such that parsing the result 
			   will not result in loss of precision
X             Hexadecimal     Formats a number to its base-16 equivalent.

Briefly consider this code:

C#
using System;
 public sealed class Program {
 public static void Main()     {
        
 Console.WriteLine("C: {0}", 39.22M.ToString("C"));
 Console.WriteLine("D: {0}", 982L.ToString("D"));
 Console.WriteLine("E: {0}", 3399283712.382387D.ToString("E"));
 Console.WriteLine("F: {0}", .993F.ToString("F"));
 Console.WriteLine("G: {0}", 32.559D.ToString("G"));
 Console.WriteLine("N: {0}", 93823713.ToString("N"));
 Console.WriteLine("P: {0}", .59837.ToString("P"));
 Console.WriteLine("R: {0}", 99.33234D.ToString("R"));
 Console.WriteLine("X: {0}", 369329.ToString("X"));
        }
      }

And notice the output using ToString:

C: $39.22
D: 982
E: 3.399284E+009
F: 0.99
G: 32.559
N: 93,823,713.00
P: 59.84 %
R: 99.33234
X: 5A2B1

Back To Enumerated Types and Bit Flags

The FileAttributes type is defined in the FCL as follows:

C#
[Flags,  Serializable]
public enum FileAttributes
{
    Archive = 0x20,
    Compressed = 0x800,
    Device = 0x40,
    Directory = 0x10,
    Encrypted = 0x4000,
    Hidden = 2,
    Normal = 0x80,
    NotContentIndexed = 0x2000,
    Offline = 0x1000,
    ReadOnly = 1,
    ReparsePoint = 0x400,
    SparseFile = 0x200,
    System = 4,
    Temporary = 0x100
}

To determine whether a file is hidden, you could execute code like this:

C#
String file = @"C:\Pagefile.sys";
FileAttributes attributes = File.GetAttributes(file);
      Console.WriteLine("Is {0} hidden? {1}", file,  
(attributes & FileAttributes.Hidden) != 0);

Here is code demonstrating how to change a file’s attributes to read-only and hidden:

C#
File.SetAttributes(@"C:\pagefile.sys", FileAttributes.ReadOnly | FileAttributes.Hidden);

Take care to note that while enumerated types and bit flags are similar, they do not have the semantics. Enums represent single numeric values, and bit flags represent a set of bits, some of which are on, some of which are off. Finally, here is an example of the use of enumerated types and bit flags using the Actions type and Enum’s static Parse method:

C#
using System;
using System.IO;

[Flags] 	
public enum Actions {
   Read   = 0x0001,
   Write  = 0x0002,
   Delete = 0x0004,
   Query  = 0x0008,
   Sync   = 0x0010 
}

public static class Program {
   public static void Main() {
      String file = @"C:\windows\system32\autoexec.NT";
      FileAttributes attributes = File.GetAttributes(file);
      Console.WriteLine("Is {0} hidden? {1}", file, 
         (attributes & FileAttributes.Hidden) != 0);

      Actions actions = Actions.Read | Actions.Write; // 0x0003
      Console.WriteLine(actions.ToString());          // "Read, Write" 

      // Because Query is defined as 8, 'a' is initialized to 8.
      Actions a = (Actions) Enum.Parse(typeof(Actions), "Query", true);

      // Because Query and Read are defined, 'a' is initialized to 9.
      a = (Actions) Enum.Parse(typeof(Actions), "Query, Read", false);

      // Creates an instance of the Actions enum with a value of 28
      a = (Actions) Enum.Parse(typeof(Actions), "28", false);
      Console.WriteLine(a.ToString());  // "Delete, Query, Sync"

      a = (Actions) Enum.Parse(typeof(Actions), "Delete, Sync");
      Console.WriteLine(a.ToString());
   }
}

Here is the output:

Is C:\windows\system32\autoexec.NT hidden? False
Read, Write
Delete, Query, Sync
Delete, Sync

Suggested Reading

  • Professional .NET Framework 2.0 by Joe Duffy
  • CLR via C# by Jeffrey Richter

History

  • 23rd July, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Monroe Community
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionUse of Enumerations Pin
vigorniensis3-May-12 4:56
professionalvigorniensis3-May-12 4:56 
GeneralMy vote of 5 Pin
Asher Barak5-Aug-09 1:11
professionalAsher Barak5-Aug-09 1:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.