Alright, let’s get our hands dirty with enums. You’ve defined a beautiful, type-safe enum to bring order to the chaos of your domain. That’s step one. Now, step two is actually using those values in the real world, which means you need to know how to ask questions about them and put them head-to-head. It’s simpler than you think, but there are a few landmines the language designers left lying around, just to keep you on your toes.

The Equality Showdown: == vs. Equals

You’ve got two enums. Are they the same? You have two primary ways to check, and for 99.9% of cases, they are functionally identical. But of course, there’s a devil in the details.

The == operator is the clear, readable winner. It’s the one you should reach for by default.

public enum CoffeeSize { Small, Medium, Large, VentiTrentaGrandeWhatever }

CoffeeSize myOrder = CoffeeSize.Large;
CoffeeSize yourOrder = CoffeeSize.Large;

if (myOrder == yourOrder)
{
    Console.WriteLine("We are caffeine siblings.");
}

This works exactly as you’d expect. Under the hood, most enums are backed by integers (the default), and == just compares those underlying numbers. Simple, fast, effective.

Now, the .Equals() method. You can use it, and it will also work.

if (myOrder.Equals(yourOrder))
{
    Console.WriteLine("Also works, but feels a bit... formal.");
}

So why prefer ==? A few reasons. First, it avoids the dreaded boxing. When you use .Equals(object obj), the enum value has to be boxed into an object for the comparison. The == operator, defined on the enum type itself, operates on the unboxed values. This is a microscopic performance hit you’ll never notice, but it’s the principle of the thing. Second, readability. == is just cleaner. There is a generic Equals<T>() method that avoids boxing, but now you’re just typing more characters to achieve the same result as ==. Just use ==.

Checking for Flags (The Bitwise Voodoo)

This is where the real fun begins. You’ve created a flags enum, a beautiful bitmask, to represent combinations of states.

[Flags]
public enum Permissions
{
    None = 0,
    Read = 1,
    Write = 2,
    Execute = 4,
    Delete = 8,
    // A combined value for convenience
    ReadWrite = Read | Write
}

Now, how do you check if a given variable userPerms includes the Read permission? This is the single most common mistake I see. Do not use ==. If you do this:

Permissions userPerms = Permissions.Read | Permissions.Write;

if (userPerms == Permissions.Read) // This is FALSE!
{
    Console.WriteLine("You can only read.");
}

That if statement evaluates to false because userPerms is a combination of flags (value = 3), which is not equal to just Read (value = 1). It’s like asking “Is this car only an engine?” when it’s actually an engine plus wheels. The correct tool for the job is the bitwise AND operator (&).

You check for the presence of a flag by ANDing the value with the flag you want and seeing if the result is not zero. The standard, canonical way to do this is:

if ((userPerms & Permissions.Read) != Permissions.None)
{
    Console.WriteLine("You have read access. Proceed.");
}

Even more readable is using the HasFlag method, introduced later:

if (userPerms.HasFlag(Permissions.Read))
{
    Console.WriteLine("This is much more intention-revealing.");
}

So why wouldn’t you always use HasFlag? It’s a matter of performance and control. HasFlag does some additional type checking and is generally slower than the raw bitwise operation. For most applications, it doesn’t matter one bit (pun intended). Use it. It’s clean. But if you’re in a performance-critical inner loop, benchmarking thousands of times a second, the raw (x & flag) != 0 check is still the king.

The is Operator and Pattern Matching

Welcome to the modern era. C# 7.0 brought pattern matching, and it’s a fantastic way to work with enums, especially in conditional blocks. The is operator provides a very clean syntax for checking a value.

CoffeeSize drink = GetCoffeeOrder();

if (drink is CoffeeSize.Large)
{
    Console.WriteLine("Charge extra. And maybe call a medic.");
}

This is functionally equivalent to drink == CoffeeSize.Large, but it reads beautifully. Where it really shines is with switch expressions, making your code look less like a tangled mess and more like a declarative work of art.

string message = drink switch
{
    CoffeeSize.Small => "A cautious choice.",
    CoffeeSize.Medium => "The default for a reason.",
    CoffeeSize.Large => "Now we're talking.",
    _ => "A size not yet known to science." // The discard catches all others
};

This is lightyears ahead of the old verbose switch statement. Use it. Embrace it.

The One Thing You Absolutely Cannot Do

Remember this: Enums have no inherent logical order. The language will happily let you use comparison operators like <, >, <=, and >= on them. This is a trap.

// Did the designers really think this was a good idea?
if (CoffeeSize.Small < CoffeeSize.Large) // This compiles. It's true.
{
    Console.WriteLine("But what does it MEAN?");
}

It means “is the underlying integer value of Small less than that of Large?” Which, in this case, it is (0 < 2). But this is almost always meaningless from a domain perspective. Is a Size.Large greater than a Size.Small? Sure. Is HttpStatusCode.OK (200) less than HttpStatusCode.NotFound (404)? Well, numerically yes, but that doesn’t mean a “Not Found” is a “greater success” than “OK”. It’s nonsense. Using comparison operators on enums is a code smell 99% of the time. If you need ordering, you should be using something else, or at the very least, be painfully aware of the underlying integer values you assigned. Just don’t do it. Your future self, trying to debug why FileMode.Append is considered “greater than” FileMode.Create, will thank you.