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.
Explicit Value Assignment
You can define your own values to enum members using the following syntax.
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.
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.
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.
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.
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.
}