Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

If statement not working as expected on combined enum value

This is a quirky one.

I have the following code...

foreach (IScanTicket ticket in this) {
    if (ticket.Status == TicketStatus.Created || ticket.Status == (TicketStatus.Transfered | TicketStatus.Created))
        return ticket;
    }
}

When I run this, where the status is Created|Transferred, the if statement seems to fail (not do what it's suppose to).

The interesting thing is that if I debug and step through the code and watch the statement, it always returns TRUE in my debugger, yet it fails to enter the block when I step through the code.

Why would the debugger show that the statement is true, yet continue like it's not? It's like what the debugger is telling me fibs.

Has anyone ever experienced this?

P.S. I'm using Xamarin studio 5.9.7

like image 336
Guy Park Avatar asked Oct 19 '22 22:10

Guy Park


1 Answers

Too long for a comment:

Actually, the [Flags] attribute does not change an enum's semantics at all, it's most popularly used by the ToString method to emit a series of names rather than a number for a combined value.

Let's say your enum was declared like this (without the Flags attribute):

enum TicketStatus
{
    Created = 1,
    Transferred = 2,
    Sold = 4
}

You could still combine different members and do any arithmetic that applies to a Flags enum:

TicketStatus status = TicketStatus.Created | TicketStatus.Transferred;

However, the following will print 3:

Console.WriteLine(status);

But if you add the [Flags] attribute, it will print Created, Transferred.

Also, it's important to note that by TicketStatus.Created | TicketStatus.Transferred you're really doing a bitwise OR on the underlying integer value, notice how in our example that the assigned values are unambiguously combinable:

Created :    0001
Transferred: 0010
Sold:        0100

Therefore a value of 3 can be unambiguously determined as a combination of Created and Transferred. However if we had this:

enum TicketStatus
{
    Created = 1,      // 0001
    Transferred = 2,  // 0010
    Sold = 3,         // 0011
}

As it is obvious by the binary representations, combining values and checking against members is problematic as combined members could be ambiguous. e.g. what is status here?

status = TicketStatus.Created | TicketStatus.Transferred;

Is it Created, Transferred or is it really Sold? However, the compiler won't complain if you try to do it, which could lead to hard to track down bugs like yours, where some check is not working as you expect it to, so it's on you to ensure the definition is sane for bitwise mixing and comparing.

On a related note, since your if statement is really only checking if the ticket has a Created status, regardless of being combined with other members, here's a better way to check for that (.NET >= 4):

status.HasFlag(TicketStatus.Created)

or (.NET <4):

(status & TicketStatus.Created) != 0

As to why your enum did not work as expected, it is almost certainly because you did not explicitly specify unambigously bitwise combinable values to its members (typically powers of two).

like image 87
Saeb Amini Avatar answered Oct 21 '22 16:10

Saeb Amini