Enum Arithmetic and Operators
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!