David
David

Reputation: 19687

The plus operator in enum definition

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

Answers (6)

Eric Lippert
Eric Lippert

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

Daniel Pratt
Daniel Pratt

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

derekaug
derekaug

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

the_joric
the_joric

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

Reed Copsey
Reed Copsey

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

plinth
plinth

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

Related Questions