C# enum types are an easy and efficient way to make an integer type without all the overhead of something like a class or even a struct. They’re basically a synonym for an integer type like byte or int. However, that “basically” hides a lot of details that affect the way you can work with them. Today’s article explores the arithmetic and operators you are and aren’t allowed to use when you opt for an enum over an int so you’ll have a better understanding of how and how not to use them.

Today’s article is based on a comment by Bernd on a previous article.

UPDATE: Webucator made a great video about this article. Check it out and also their online C# classes.

First a very short primer on enum in case you’re not familiar with how to declare and create one. Just use the enum keyword to make a new enum type anywhere you’d create a class, interface, or struct:

enum Day { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday }

Then you can create one using the . syntax like the value was a static field of the enum type:

Day first = Day.Sunday;

When declaring the enum type, you have two additional options. First, you can specify the type of integer that the enum uses to hold its values. For example, here’s one that uses an unsigned 8-byte value:

enum Day : byte { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday }

The types you’re allowed to use are: byte, sbyte, short, ushort, int, uint, long, and ulong.

Second, if you don’t want the values to start with zero, one, two, and so forth, you can specify the values explicitly:

enum Day { Sunday=10, Monday=20, Tuesday=30, Wednesday=40, Thursday=50, Friday=60, Saturday=70 }

Finally, if you’re using the enum type to represent bitwise flags then you can use the [Flags] attribute to help out your IDE and get better ToString printing:

[Flags]
enum FilePermission { None=0, Read=1, Write=2, Execute=4 }

With that in mind, let’s explore what operators we can use on enum types to manipulate them. Clearly, we can use a huge array of them on integer types like int and byte, but which ones are allowed with enum types? To answer that, let’s make a little script and try to compile it with Unity:

enum CardValue : sbyte
{
	Ace,
	One,
	Two,
	Three,
	Four,
	Five,
	Six,
	Seven,
	Eight,
	Nine,
	Ten,
	Jack,
	Queen,
	King,
	MinusSix = -6
}
 
var report = "";
var five = CardValue.Five;
var six = CardValue.Six;
report += "five == six: " + (five == six) + "\n";
report += "five != six: " + (five != six) + "\n";
report += "five < six: " + (five < six) + "\n";
report += "five > six: " + (five > six) + "\n";
report += "five <= six: " + (five <= six) + "\n";
report += "five >= six: " + (five >= six) + "\n";
report += "five + six: " + (five + six) + "\n";
report += "five + 6: " + (five + 6) + "\n";
report += "five * six: " + (five * six) + "\n";
report += "five * 6: " + (five * 6) + "\n";
report += "five / six: " + (five / six) + "\n";
report += "five / 6: " + (five / 6) + "\n";
report += "five % six: " + (five % six) + "\n";
report += "five % 6: " + (five % 6) + "\n";
report += "five << six: " + (five << six) + "\n";
report += "five << 6: " + (five << 6) + "\n";
report += "five >> six: " + (five >> six) + "\n";
report += "five >> 6: " + (five >> 6) + "\n";
report += "six - five: " + (six - five) + "\n";
report += "five ^ six: " + (five ^ six) + "\n";
report += "five & six: " + (five & six) + "\n";
report += "five | six: " + (five | six) + "\n";
report += "~five: " + (~five) + "\n";
report += "-five: " + (-five) + "\n";
five++;
report += "five++: " + five + "\n";
five = CardValue.Five;
five--;
report += "five--: " + five + "\n";
five = CardValue.Five;
report += "sizeof(CardValue): " + sizeof(CardValue) + "\n";
Debug.Log(report);

Right away we get a bunch of compiler errors because many of the operators aren’t allowed. The following chart shows the operators that give compiler errors:

Operator enum {operator} enum enum {operator} 6
x + y Error OK
x – y Error Error
x * y Error Error
x / y Error Error
x % y Error Error
x << y Error Error
x >> y Error Error
-x Error Error

It’s strange that we can’t add two enum values together but we can add an enum value and a literal value or a value of the same integer type the enum is based on (sbyte in this case).

With those operators removed we have the following script:

var report = "";
var five = CardValue.Five;
var six = CardValue.Six;
report += "five == six: " + (five == six) + "\n";
report += "five != six: " + (five != six) + "\n";
report += "five < six: " + (five < six) + "\n";
report += "five > six: " + (five > six) + "\n";
report += "five <= six: " + (five <= six) + "\n";
report += "five >= six: " + (five >= six) + "\n";
report += "five + 6: " + (five + 6) + "\n";
report += "six - five: " + (six - five) + "\n";
report += "five ^ six: " + (five ^ six) + "\n";
report += "five & six: " + (five & six) + "\n";
report += "five | six: " + (five | six) + "\n";
report += "~five: " + (~five) + "\n";
five++;
report += "five++: " + five + "\n";
five = CardValue.Five;
five--;
report += "five--: " + five + "\n";
five = CardValue.Five;
report += "sizeof(CardValue): " + sizeof(CardValue) + "\n";
Debug.Log(report);

Which prints this:

five == six: False
five != six: True
five < six: True
five > six: False
five <= six: True
five >= six: False
five + 6: Jack
six - five: 1
five ^ six: Three
five & six: Four
five | six: Seven
~five: MinusSix
five++: Six
five--: Four
sizeof(CardValue): 1

Most of these are straightforward and work the way you’d expect if you just imagine the enum values being names for plain integers. Some of them, however, are a little strange.

First, enum - enum does not yield another enum the way that enum + 6 does. Instead, you get an integer value of the same type that the enum is based on. That means that with CardValue you get an sbyte that you’ll need to cast back to the enum type if that’s what you’re after:

CardValue difference = (CardValue)(six - five);

This isn’t even consistent with the bitwise ^, &, |, or ~ operators or the increment and decrement operators: ++ and --. It’s close to being consistent with the sizeof operator, but even that returns an int regardless of which integer type the enum is based on.

Finally, keep in mind that enum arithmetic can easily result in values that aren’t in the enum. After all, these are just integers behind the scenes. In the following example I go beyond the last value and before the first just by adding:

five + 60 // 65
five - 60 // -55

While ToString() prints the integer value rather than the enum name for the value (because there isn’t one), the type of the expression CardValue + 60 is still CardValue. This means you can get back to a named value just by doing more arithmetic:

five + 60 - 60 // Five

That about wraps up today’s coverage of enum types and the operators you can use with them. If you know of any quirks related to enum types, feel free to leave a comment!