Reputation: 19687
I stumbled upon the usage of the plus (+) operator in an enum definition today, I was surprised to see the accompanying tests pass. Anyone have any idea where this may be documented?
public enum ApprovalItemState
{
Enqueued = 1,
Approved = 2,
Denied = 4,
Acknowledged = 8,
ApprovalAcknowledged = ApprovalItemState.Approved + ApprovalItemState.Acknowledged,
DenialAcknowledged = ApprovalItemState.Denied + ApprovalItemState.Acknowledged
}
[TestClass]
public class ApprovalItemStateTests
{
[TestMethod]
public void AreFlagsDeniedAndAcknowledged()
{
Assert.AreEqual(ApprovalItemState.DenialAcknowledged, ApprovalItemState.Denied | ApprovalItemState.Acknowledged);
}
[TestMethod]
public void IsDenialAcknowledged()
{
Assert.IsTrue(Enum.IsDefined(typeof(ApprovalItemState), ApprovalItemState.Denied | ApprovalItemState.Acknowledged));
Assert.AreEqual(ApprovalItemState.Denied | ApprovalItemState.Acknowledged, (ApprovalItemState)Enum.Parse(typeof(ApprovalItemState), "DenialAcknowledged"));
}
[TestMethod]
public void IsNotDeniedAndApproved()
{
Assert.IsFalse(Enum.IsDefined(typeof(ApprovalItemState), ApprovalItemState.Approved | ApprovalItemState.Denied));
}
}
Upvotes: 6
Views: 3441
Reputation: 660098
Reed's answer is of course correct. I just thought I'd add an interesting bit of trivia. First off, when you are inside the enum, all the members of the enum are in scope. This is the only situation in C# in which you can use an enum member via its unqualified name!
public enum ApprovalItemState
{
Enqueued = 1,
Approved = 2,
Denied = 4,
Acknowledged = 8,
ApprovalAcknowledged = Approved | Acknowledged,
DenialAcknowledged = Denied | Acknowledged
}
The second trivia point is that the C# compiler actually allows enum arithmetic involving other enums inside an enum!
enum E
{
E1
}
enum F
{
F1
}
enum G
{
G1 = E.E1 + F.F1
}
Normally that would not be at all legal; you cannot add two dissimilar enums together and you cannot assign the result. The compiler relaxes those rules inside an enum initializer so that you can do things like:
enum MyFlags
{
MyReadOnly = FileFlags.ReadOnly,
...
Upvotes: 11
Reputation: 12077
From the C# reference on enum:
...Every enumeration type has an underlying type, which can be any integral type except char. The default underlying type of the enumeration elements is int...
By the way, it is more idiomatic (and less error prone) to use |
instead of +
to combine enum flag values. For example, this mistake won't cause a problem:
DenialAcknowledged =
ApprovalItemState.Denied
| ApprovalItemState.Acknowledged
| ApprovalItemState.Denied
But this mistake will cause a problem:
DenialAcknowledged =
ApprovalItemState.Denied
+ ApprovalItemState.Acknowledged
+ ApprovalItemState.Denied
Upvotes: 2
Reputation: 2145
I'll break down one of these for you.
DenialAcknowledged = ApprovalItemState.Denied + ApprovalItemState.Acknowledged
DenialAcknowledged = 4 + 8
DenialAcknowledged = 12
For this test:
[TestMethod]
public void AreFlagsDeniedAndAcknowledged()
{
Assert.AreEqual(ApprovalItemState.DenialAcknowledged, ApprovalItemState.Denied | ApprovalItemState.Acknowledged);
}
You're checking:
ApprovalItemState.DenialAcknowledged == ApprovalItemState.Denied | ApprovalItemState.Acknowledged
12 == 4 | 8
12 == 0100 | 1000 //bitwise operation
12 == 1100
12 == 12 //convert from binary to decimal
And that is why the test pass. Not exactly straightforward by looking at the code.
Upvotes: 0
Reputation: 12226
Approved + Acknowledged
is just a constant so it can be assigned as a value to enum element.
Regarding tests -- they work because int values are "happy ones", so (a + b) == (a | b)
However if your change that to something like that:
public enum ApprovalItemState
{
Enqueued = 1,
Approved = 2,
Denied = 7,
Acknowledged = 18,
ApprovalAcknowledged = Approved + Acknowledged,
DenialAcknowledged = Denied + Acknowledged
}
and tests won't pass.
Upvotes: 1
Reputation: 564413
The C# Language Spec, in 14.5, states:
The following operators can be used on values of enum types: ==, !=, <, >, <=, >= (§7.10.5), binary + (§7.8.4), binary ‑ (§7.8.5), ^, &, | (§7.11.2), ~ (§7.7.4), ++ and -- (§7.6.9 and §7.7.5).
Basically, since the enum is internally stored as an Int32
(that's the default, unless you specify a different storage type), you can use addition like this.
However, it's far more common to use |
instead of +
to define masks. Also, it would be common to include [Flags]
if you're going to use this as a flags enumeration.
Upvotes: 10
Reputation: 49189
It's not surprising - enums are represented by integral types. You can use other operators as well, although if you're going to use flags (which this example is doing), it's far better to use the [Flags] attribute to define them and better to lay out the bits more clearly:
[Flags]
public enum ApprovalItemState
{
Enqueued = 1 << 0,
Approved = 1 << 1,
Denied = 1 << 2,
Acknowledged = 1 << 3,
ApprovalAcknowledged = ApprovalItemState.Approved | ApprovalItemState.Acknowledged,
DenialAcknowledged = ApprovalItemState.Denied | ApprovalItemState.Acknowledged
}
Upvotes: 0