C# Programming · Lesson 10

Creating Lists of Items with Enumerations

Model stable sets of named values in C# with enum, initializers, underlying types, flags, enumerated switches, and extension methods.

  • csharp
  • enums
  • enumerations
  • flags
  • switch
  • extension-methods

Lesson purpose

To enumerate means to list individual items. Lesson 10 shows how the enum keyword creates a named list of related values, such as Colors.Red and Colors.Blue, so code can model the real world in a form people understand. Students decide when an enumeration is appropriate, declare enumerations with default values, initializers, and a chosen underlying type, list their names and values, combine options with [Flags] and bitwise operators, make switch statements readable with enumerated cases, and add behavior to an enumeration with an extension method. Enumerations first appeared as a preview in Lesson 5 and supported the priority queue in Lesson 8.

Learning objectives

  • Explain what an enumeration is and recognize real-world enumerations.
  • Apply the three rules of thumb (collection stability, member stability, consistent value) to decide whether to create an enumeration.
  • Declare an enumeration with enum and explain its default values.
  • Display a member’s name and its numeric value, and list all names and values with Enum.GetNames and Enum.GetValues.
  • Step through a range of members with a for loop.
  • Assign values with initializers, including expressions, hexadecimal literals, and bitwise OR.
  • Specify an enumeration’s underlying integral type and verify it with GetTypeCode().
  • Build a [Flags] enumeration from bit positions and combine options with |.
  • Replace magic numbers in a switch with enumeration members.
  • Extend an enumeration with an extension method.

1. Enumerations in the real world

An enumeration is a permanent collection of named items. Examples:

  • Colors: people enumerate colors on a color wheel and group them as complementary or analogous.
  • Biological classification: the five kingdoms, and the phyla within each, follow a pattern and rarely change.
  • U.S. states: an application that addresses packages can offer a state enumeration so every address uses a valid, correctly spelled state.

Enumerations model real objects correctly, reduce human error, and save time. In code, Colors.Blue communicates meaning far better than the number 4.

2. When to use an enumeration

Enumerations work only under certain circumstances. Three rules of thumb:

Rule Meaning Good fit Poor fit
Collection stability The list of members must be stable and unchanging. U.S. states This week’s top-ten songs
Member stability Each member must stay recognizable and consistent. Area codes People’s first names
Consistent value Each word must keep a dependable numeric value. Days of the week Any list whose numbers get reassigned

An enumeration trades numbers a program understands for words a human understands. If the number behind a word changes, stored data and code that depend on the old number break.

Quick check: should an application store its product catalog as an enumeration? Why or why not?

3. Declaring and using an enumeration

enum Colors { Red, Orange, Yellow, Green, Blue, Purple }
  • enum names the collection (Colors) and lists its members in braces.
  • By default the first member is 0 and each later member is one more: Red = 0, Orange = 1, … Purple = 5.
  • The book’s declaration ends with a semicolon after the closing brace; that semicolon is allowed but not required.
  • An enumeration is a type, so declare it at the class or namespace level, not inside a method.

Accessing one member gives either its name or its number:

Console.WriteLine($"Color Name: {Colors.Blue}.");         // Color Name: Blue.
Console.WriteLine($"Color Value: {(int)Colors.Blue}.");   // Color Value: 4.

Printing a member shows its name; casting to int shows its number. Blue is the fifth member, so its value is 4.

Listing every member

Console.WriteLine("All Color Names:");
foreach (string item in Enum.GetNames(typeof(Colors)))
{
    Console.WriteLine(item);
}

Console.WriteLine("All Color Values:");
foreach (Colors item in Enum.GetValues(typeof(Colors)))
{
    Console.WriteLine($"{item} = {(int)item}");   // Red = 0 … Purple = 5
}
  • typeof(Colors) supplies the enumeration’s type to the method.
  • Enum.GetNames returns the names as strings.
  • Enum.GetValues returns the members themselves, which can be printed by name or cast to a number.

Stepping through a range

Because members have numeric values, they can be compared and incremented:

for (Colors item = Colors.Orange; item <= Colors.Blue; item++)
{
    Console.WriteLine("{0} = {1}", item, (int)item);
}
// Orange = 1
// Yellow = 2
// Green = 3
// Blue = 4

Prediction: what prints if the condition becomes item < Colors.Blue?

4. Initializers

Default values are fine when only the readable name matters. When specific values matter, an initializer assigns each member’s value:

enum Colors2
{
    Red = 5,
    Orange = 10,
    Yellow = Orange + 5,     // 15: refers to another member
    Green = 5 * 4,           // 20: an arithmetic expression
    Blue = 0x19,             // 25: hexadecimal literal
    Purple = Orange | Green  // 30: bitwise OR of 10 and 20
}

foreach (Colors2 item in Enum.GetValues(typeof(Colors2)))
{
    Console.WriteLine($"{item} = {(int)item}");
}
// Red = 5, Orange = 10, Yellow = 15, Green = 20, Blue = 25, Purple = 30
  • An initializer can be any constant expression that produces an integer; it cannot be text such as "Hello".
  • 0x19 is 1 × 16 + 9 = 25.
  • Orange | Green combines the bits of 10 (01010) and 20 (10100) into 11110, which is 30.
  • A member without an initializer is one more than the member before it.

Trace the bitwise OR on the board in binary; it prepares students for flags.

5. Specifying the underlying type

The default underlying type is int. An enumeration can instead use any integral type: byte, sbyte, short, ushort, int, uint, long, or ulong. Choose based on how the enumeration will be used and how many values it needs.

enum Colors3 : byte { Red, Orange, Yellow, Green, Blue, Purple }

foreach (Colors3 item in Enum.GetValues(typeof(Colors3)))
{
    Console.WriteLine($"{item} is {item.GetTypeCode()} = {(int)item}");
}
// Red is Byte = 0 … Purple is Byte = 5
  • A colon and a type name after the enumeration name set the underlying type.
  • GetTypeCode() reports the underlying type (Byte). GetType() would report the enumeration type itself (Colors3).
  • float, double, and decimal cannot be used for an enumeration; the runtime allows only integral types.

6. Enumerated flags

Flags represent options that are not mutually exclusive, such as car accessories: a buyer may want air conditioning, GPS, and Bluetooth in any combination. Think in bits: a byte holds eight separate on/off positions, not just one number from 0 to 255.

0000 0001  Air conditioning  (1)
0000 0010  GPS               (2)
0000 0100  Bluetooth         (4)
0000 0011  Air conditioning and GPS (3)

Each option owns one bit position, so its value is a power of two. Bit manipulation uses AND (&), OR (|), and exclusive OR (^).

Hexadecimal is the usual notation because each hex digit represents exactly four bits: 0x11 is 0001 0001. Hex digits run 0–9, then A = 10 through F = 15.

[Flags]
enum Colors4
{
    Red    = 0x01,
    Orange = 0x02,
    Yellow = 0x04,
    Green  = 0x08,
    Blue   = 0x10,
    Purple = 0x20
}

Colors4 myColors = Colors4.Red | Colors4.Green | Colors4.Purple;

Console.WriteLine(myColors);                      // Red, Green, Purple
Console.WriteLine("0x{0:X2}", (int)myColors);    // 0x29
  • [Flags] is an attribute: an instruction that tells the compiler to treat a structure in a special way. Here it marks the enumeration as a set of combinable flags.
  • Hex initializers are not required but make the bit positions easy to read.
  • Combine options with |. Red (1) + Green (8) + Purple (32) = 41, which is 0x29.
  • Because of [Flags], printing the variable lists the individual options instead of the number 41.
  • The format string X2 prints the value as two hexadecimal digits; a colon separates the argument index from the format string.

Testing and removing flags (a natural extension of the book’s example):

bool hasGreen = (myColors & Colors4.Green) == Colors4.Green;   // true
bool hasBlue  = myColors.HasFlag(Colors4.Blue);                // false

myColors &= ~Colors4.Red;                                      // turn Red off
Console.WriteLine(myColors);                                   // Green, Purple

& keeps only bits present in both operands; ~ flips every bit, so & ~Colors4.Red clears just the Red bit.

Quick check: what value and output result from Colors4.Orange | Colors4.Blue?

7. Enumerated switches

A switch on bare numbers hides the reason for each decision:

int mySelection = 2;
switch (mySelection)
{
    case 0:
        Console.WriteLine("You chose red.");
        break;
    case 2:
        Console.WriteLine("You chose yellow.");
        break;
    // … cases 1, 3, 4, 5 …
}

Why is the selection 2? What does case 4 mean? Magic numbers like these are a good way to create hard-to-find bugs. An enumerated switch makes the intent visible:

Colors myColorSelection = Colors.Yellow;
switch (myColorSelection)
{
    case Colors.Red:
        Console.WriteLine("You chose red.");
        break;
    case Colors.Orange:
        Console.WriteLine("You chose orange.");
        break;
    case Colors.Yellow:
        Console.WriteLine("You chose yellow.");
        break;
    case Colors.Green:
        Console.WriteLine("You chose green.");
        break;
    case Colors.Blue:
        Console.WriteLine("You chose blue.");
        break;
    case Colors.Purple:
        Console.WriteLine("You chose purple.");
        break;
}

Both versions print “You chose yellow.”, but the second shows that the variable holds a color and why each path runs. Enumeration members are constants, so they satisfy the switch rule from Lesson 5 that case labels must be known at compile time. Add a default case to handle any value outside the named members.

8. Enumeration methods (extension methods)

An enumeration cannot contain methods, but an extension method can add behavior to it:

public enum Colors { Red, Orange, Yellow, Green, Blue, Purple }

public static class Extensions
{
    public static string GetNameValue(this Colors color)
    {
        return $"{color} is {color.GetTypeCode()} = {(long)color}";
    }
}

foreach (Colors color in Enum.GetValues(typeof(Colors)))
{
    Console.WriteLine(color.GetNameValue());   // Red is Int32 = 0 …
}
  • The method lives in a static class and is itself static.
  • The this keyword before the first parameter makes the method callable as if it belonged to Colors: color.GetNameValue().
  • No argument is passed in the call because the object before the dot becomes the this Colors color parameter. Lesson 13 explains this fully.
  • Casting to long works for every integral underlying type.

Other useful Enum methods beyond the book’s examples:

Method Purpose
Enum.GetNames(typeof(T)) All member names as strings
Enum.GetValues(typeof(T)) All members
Enum.Parse / Enum.TryParse Convert a name string to a member
Enum.IsDefined(typeof(T), value) Test whether a value or name exists
member.ToString() The member’s name
member.HasFlag(flag) Test a flag

Classroom application

  1. Create enum DayOfClass { Monday, Tuesday, Wednesday, Thursday, Friday } and print each name and value with Enum.GetValues.
  2. Rewrite a numeric menu (1 = add, 2 = delete, 3 = list) as a switch on enum MenuChoice, and parse the user’s typed choice with Enum.TryParse.
  3. Give an OrderStatus enumeration explicit initializers (Pending = 10, Shipped = 20, Delivered = 30) and explain why stored data depends on those numbers never changing.
  4. Build a [Flags] enum Accessories { None = 0, AirConditioning = 0x01, Gps = 0x02, Bluetooth = 0x04 }, combine choices with |, test them with HasFlag, and print the combined value in hex.
  5. Write an extension method that returns a friendly description for each accessory.

Decide whether each list should be an enumeration: U.S. states, current employee names, card suits, daily stock prices.

Common misconceptions

  • Default values start at 0, not 1.
  • Printing a member shows its name; casting to int shows its number.
  • Initializers must produce integers; strings and floating-point values are not allowed.
  • A member without an initializer takes the previous member’s value plus one.
  • GetTypeCode() reports the underlying type; GetType() reports the enumeration type.
  • Flag values must be distinct powers of two; combine them with |, not + or &.
  • [Flags] changes how a combined value is displayed; the bit arithmetic works either way.
  • A byte holds values from 0 through 255, or eight separate flag bits.
  • Enumerations suit stable lists; volatile data belongs in a collection or database.
  • An enumeration cannot declare methods directly; extension methods add behavior from outside.

Lesson summary

An enumeration replaces numbers the program needs with names people understand. It fits collections whose members and values are stable. enum declares the list with default values starting at 0, initializers set specific values, and a colon selects the underlying integral type. Enum.GetNames, Enum.GetValues, and for loops list the members. [Flags] enumerations assign each option a bit so | can combine them, enumerated switch statements make decisions readable, and extension methods add behavior. Lesson 11 begins Part 2 with classes and objects.