2.11. The enum Value Type

Often when we program we need to define a set of alternative attributes to associate with an object. A file, for example, might be open in one of three states: input, output, and append. One strategy to keep track of these state values is to associate a unique constant number with each one. Thus we might write

public class fileMode
{
      public const int input  = 1;
      public const int output = 2;
      public const int append = 3;
      // ...
}

and use these constants as follows:

bool open_file( string file_name, int open_mode);
// ...
open_file( "Phoenix_and_the_Crane", fileMode.append );

The benefit is that it is much easier to use a set of mnemonic constant objects than to remember and make sense of the associated literal values. The drawback is that there is no way to constrain the input values to only those of input, output, and append. The compiler, for example, happily accepts an input value of 1024 to open_file(); the burden of constraining the accepted values falls to the programmer.

An enum type defines a group of related named integral constants. An object of enum type can be assigned only one of those named constants. We define an enum type with the enum keyword, followed by the enum type identifier. Individual values are identified within a comma-separated list of named enumerators placed within curly braces. Each enumerator must have a unique name; however, multiple enumerators can be associated with the same value.

By default, the first enumerator is assigned 0. Each subsequent enumerator is incremented by one. To override the default assignment, we simply assign the enumerator a constant integral expression—for example,

enum open_modes{
   input = 1, output, append,
   last_input_mode = input, last_output_mode = append };

Within the enum, the enumerators can be referenced without qualification. Outside the enum, however, an enumerator must be qualified with the type of its enum: open_modes.input. A nested enum must be qualified with the name of the class as well: MyFileClass.open_modes.input.

If we insist on assigning an integral constant expression to an enum object, we must explicitly cast it to the enum type:

void open_file( string file_name, open_modes om );

open_modes om;
string     fname;
// ... set om and fname ...

open_file( fname, om );                // OK
open_file( fname, open_modes.append ); // OK
open_file( fname, 1 );                 // error!
open_file( fname, (open_modes)1 );     // OK, but ...

This overrides any type checking of the value. It is not a recommended practice.

We can use an enum type to represent the possible trace modes of our WordCount class—for example,

public class WordCount
{
    public enum traceFlags { turnOff, toConsole, toFile };
    private traceFlags m_trace;

On the basis of the command-line options, the Main() entry point determines which of the three values to use for initializing m_trace:

static public void Main( string [] args )
{
   WordCount.traceFlags traceOn =
          WordCount.traceFlags.turnOff;
   // ...
   
   foreach ( string option in args )
          switch ( option )
         {
                case "-t":
                      traceOn = WordCount.traceFlags.toConsole;
                break;

                case "-tf":
                      traceOn = WordCount.traceFlags.toFile;
                break;

               // ...

Enumerators are natural targets of switch statement case labels. For example, in the openFile() method of WordCount, we create a trace object that is bound either to the console or to a file based on m_trace:

if( m_trace != traceFlags.turnOff )
      switch ( m_trace )
      {
          case traceFlags.toConsole:
                 cout = new TextWriterTraceListener(Console.Out);
               Trace.Listeners.Add( cout );
          break;

          case traceFlags.toFile:
                m_tracer = File.CreateText(m_diag_file);
                cout = new TextWriterTraceListener(m_tracer);
               Trace.Listeners.Add( cout );
          break;
      }

By default, the type representation of each enumerator is int. We can override that type by specifying an alternative integral type, provided that the type we specify can represent all the enumerator values—for example,

public enum weekdays : byte
{
    sunday, monday, tuesday, wednesday,
    thursday, friday, saturday
};

We can iterate across the enumerators using the increment (or decrement) operator, as in the following example:

public static void translator( string [] foreign )
{
    weekdays wd = weekdays.sunday;

    for ( ; wd <= weekdays.saturday; ++wd )
        Console.WriteLine( wd + " : " + foreign[(int)wd] );
}

foreign represents a string array of the days of the week in a foreign (i.e., non-English) language. translator() prints out the days of the week in both English and the foreign language represented by the array. Notice that the indexing of the array requires an explicit cast of the wd enum object. After the code has been compiled and executed, the following output is generated:

sunday : dimanche
monday : lundi
tuesday : mardi
wednesday : mercredi
thursday : jeudi
friday : vendredi
saturday : samedi

The .NET class framework uses enum types extensively to encapsulate alternative modes or attributes of class properties, such as BorderStyle, TextBoxMode, FontSize, and so on.

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

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