Enums

A dive into enumeration types, commonly known as enums.

What are they?

Enumeration types, also known as enums, are value types that define a fixed set of constant, integral values. There are two types of enums: simple enums and flag enums.

Simple Enums

Simple enums represent a fixed set of values from which you can choose only one at a time. For example, days of the week could be represented as a simple enum because the number of days in the week is fixed and it cannot be more than one day of the week at a time.

Here's an example of a simple enum. Enum members are always assigned an underlying integral value. If you don't explicitly assign the values yourself, members will be assigned values implicitly, with the first defined member being assigned to zero and all subsequent members being incremented by one.

enum DayOfWeek
{
    Sunday, // 0
    Monday, // 1
    Tuesday, // 2
    Wednesday, // 3
    Thursday, // 4
    Friday, // 5
    Saturday // 6
}
Basic enum example

Explicit Value Assignment

You can define your own values to enum members using the following syntax.

enum HttpStatusCode
{
    Ok = 200,
    NotFound = 404,
    ImATeaPot = 418
}
Enum with explicit values

Naming Simple Enums

When designing a simple enum, use singular nouns or noun phrases. For example, DayOfWeek  not DaysOfWeek.

Flag Enums

Flag enums also represent a small, fixed set of values, but you can select more than one at a time. For example, input from a video game controller could be represented as a flag enum, as there are a fixed number of buttons but more than one might be pressed at a time.

This is an example of how flag enums can be defined in code. Here you can see directions that correspond to buttons that can be pressed on a video game controller. The directional pad on this controller can be pressed in a single direction like UP, or it can be pressed in multiple directions like UP and RIGHT.

[Flags]
enum DirectionKeys
{
    None = 0b_0000_0000, // 0
    Up = 0b_0000_0001, // 1
    Down = 0b_0000_0010, // 2
    Left = 0b_0000_0100, // 4
    Right = 0b_0000_1000 // 8
}
Flags enum example

None (Zero) Value

Note how we've included a None value to represent a state when none of the flags have been raised (all bits are set to off).

Powers of Two

For flag enums to work properly, each member's value should be assigned to a value that is a power of two (1, 2, 4, 8, 16, 32, etc). This ensures that each member lines up to a specific individual bit, because each bit also represents a value that is a power of two.

Flags Attribute

Additionally, the Flags attribute has been added at the top of the declaration. This attribute isn't required for the flag enum to work – all it really does is produce better looking string representations of the flag values. See the example below.

// This will print "Up, Left" if DirectionKeys
// has the [Flags] attribute.
// This will print, "5" if DirectionKeys
// does not have the [Flags] attribute.
Console.WriteLine(DirectionKeys.Up | DirectionKeys.Left);
Flags attribute example

Bit Flags

Flag enums take advantage of bit positioning to activate and deactivate specific bits selectively. You can assign arbitrary meanings to each bit and then turn them on or off to change state.

Byte representation

This illustration shows the flag enum example that we saw previously with the video game controller buttons. Each bit has been mapped to a specific button that can be turned either on or off.

Byte mapped using bit flags

We can use code to raise a single flag at a time. Let's try flipping the `Down` bit.

var direction = DirectionKeys.Down;
Console.WriteLine(direction); // Prints "Down"

In this illustration, the bit representing Down has been flipped to its on position.

Now let's also turn on the bit that represents the Right direction. This can be accomplished using a bitwise operation.

var direction = DirectionKeys.Down;
direction = direction | DirectionKeys.Right;
Console.WriteLine(direction); // Prints "Down, Right"

In this example, both the Down and Right bits have been flipped to their on positions.

Naming Flag Enums

Because flag enums can represent multiple values at a time, use plural nouns or noun phrases when naming them. For example, ControllerButtons not ControllerButton.

System.Enum

The System.Enum type is the abstract base class from which all `enum` types are derived. When you declare a new enum, it will automatically inherit from this class and thus you should not try to extend the class anywhere in your code.

Generic Type Constraint

If you're using generic parameters in C# 7.3 or later, you can specify System.Enum as a constraint for the parameter in your generic type.

public void DoSomethingWithAnEnum<T>(T value)
    where T : System.Enum
{
    // This code is guaranteed to receive an enum.
}

Conversions

You can easily and efficiently convert an enum value to its underlying integral type by casting it.

Console.WriteLine(DirectionKeys.Left); // Prints "Left"
Console.WriteLine((int)DirectionKeys.Left); // Prints "4"

You can get the member name of the enum by simply calling ToString.

string directionName = DirectionKeys.Right.ToString();
Console.WriteLine(directionName); // Prints "Right"

If you're using the ToString method on your enum frequently, you should know that there is a slight performance hit for this. And if you feel the need to do this frequently, you might want to consider using string constants instead, as they are around 400 times faster than invoking ToString on an enum.

Casting Integral Values To Enums

Enums are not much more than constant integral values. As a result, it's possible to cast an integral value to your enum type, regardless of whether or not your enum has a member that corresponds to the integral value.

[Flags]
enum DirectionKeys
{
    None = 0, Up = 1, Down = 2, Left = 4, Right = 8
}

static void AbuseEnums()
{
    DirectionKeys keys = (DirectionKeys)1;
    Console.WriteLine(keys); // Prints "Up"
    
    // The compiler has no issue with this!
    keys = (DirectionKeys)16;
    Console.WriteLine(keys); // Prints "16"
}

You can use the Enum.IsDefined method to ensure that the enum value you receive is legitimate.

static void ProcessKeys(DirectionKeys keys)
{
    if (!Enum.IsDefined(typeof(DirectionKeys), keys))
        throw new ArgumentException("The enum value is not defined!", nameof(keys));
    // Continue safely processing input here.
}