Enums & Flags

10 questions found

An enum is a distinct value type representing a named set of constant integral values; by default, its members are backed by 'int', starting at 0 and auto-incrementing, unless you assign explicit values.
public enum DayOfWeek {
  Sunday,    // 0
  Monday,    // 1
  Tuesday    // 2
}
DayOfWeek today = DayOfWeek.Monday;
Console.WriteLine((int)today); // 1
Real-world example Representing a fixed set of order statuses, days of the week, or log severity levels with meaningful names.

Common follow-ups: Can you specify a different underlying type for an enum, like byte or long?

Fundamentals
Cast directly with (int) to get the numeric value; use ToString() to get the member's name as a string, and Enum.Parse<T>() (or the safer Enum.TryParse<T>()) to convert a string back into the corresponding enum value.
DayOfWeek day = DayOfWeek.Monday;
int numericValue = (int)day;          // 1
string name = day.ToString();          // "Monday"
DayOfWeek parsed = Enum.Parse<DayOfWeek>("Tuesday"); // DayOfWeek.Tuesday
Real-world example Converting a user-selected string from a dropdown into the corresponding enum value for use in business logic.

Common follow-ups: What exception does Enum.Parse<T>() throw if the input string doesn't match any enum member, and how does TryParse avoid it?

String Handling & StringBuilder
Mark the enum with [Flags] and assign each member a distinct POWER-OF-TWO value, letting you safely combine multiple flags into a single value with the bitwise OR operator and check for a specific flag's presence with bitwise AND.
[Flags]
public enum Permissions {
  None = 0,
  Read = 1,
  Write = 2,
  Delete = 4,
  All = Read | Write | Delete
}
Permissions userPerms = Permissions.Read | Permissions.Write;
bool canWrite = (userPerms & Permissions.Write) == Permissions.Write; // true
Real-world example Modeling a user's combined set of permissions (read/write/delete) compactly as a single stored enum value.

Common follow-ups: What does the [Flags] attribute actually change about the enum's behavior beyond just documentation intent?

Attributes & Reflection
Without [Flags], calling ToString() on a value that doesn't exactly match any single named member just returns the raw numeric value as a string; WITH [Flags], the runtime recognizes the value as a combination and formats it as a comma-separated list of the individual flag names that make it up.
[Flags]
public enum Permissions { None = 0, Read = 1, Write = 2, Delete = 4 }
var combined = Permissions.Read | Permissions.Write;
Console.WriteLine(combined.ToString()); // "Read, Write" -- thanks to [Flags]

// Without [Flags], the same combined value would just print "3"
Real-world example Displaying a human-readable summary of a user's combined permissions in a log message or admin dashboard.

Common follow-ups: How would you use HasFlag() as an alternative, more readable way to check for a specific flag's presence?

String Handling & StringBuilder
Enum.HasFlag() involves BOXING the enum value (converting it to an object) internally in older .NET versions, making it measurably slower than a direct bitwise AND comparison in performance-sensitive, high-frequency code — modern .NET has optimized this significantly, but the direct bitwise check remains marginally faster and avoids any boxing concern entirely.
[Flags]
public enum Permissions { None = 0, Read = 1, Write = 2 }
var perms = Permissions.Read;

// Slightly slower historically (boxing), but more readable
bool hasRead1 = perms.HasFlag(Permissions.Read);

// Faster, no boxing, less readable
bool hasRead2 = (perms & Permissions.Read) == Permissions.Read;
Real-world example Choosing the direct bitwise check over HasFlag() specifically in a tight, performance-critical loop checking permissions millions of times.

Common follow-ups: At what call frequency does this performance difference actually become practically noticeable in a real application?

Memory & Garbage Collection
Define generic (or type-specific) extension methods that encapsulate the bitwise OR (for adding) and AND-with-complement (for removing) operations, giving calling code a clean, readable API instead of repeating raw bitwise expressions everywhere.
public static class PermissionsExtensions {
  public static Permissions AddFlag(this Permissions value, Permissions flag) => value | flag;
  public static Permissions RemoveFlag(this Permissions value, Permissions flag) => value & ~flag;
}

var perms = Permissions.Read;
perms = perms.AddFlag(Permissions.Write);      // Read, Write
perms = perms.RemoveFlag(Permissions.Read);    // Write
Real-world example Providing a clean, discoverable API for manipulating a permissions or feature-flags enum throughout a large codebase.

Common follow-ups: Could this same pattern be made generic to work with ANY [Flags] enum type, not just Permissions specifically?

Extension Methods
Since C# enums are just named integers, a value like (Permissions)999 compiles and runs fine even though it doesn't correspond to any defined member (or valid flag combination) — use Enum.IsDefined() for simple enums, or a manual bitwise check against a computed 'all valid flags' mask for [Flags] enums, to validate untrusted input before trusting it.
public enum Status { Active = 0, Inactive = 1 }
int rawValue = 99; // e.g., from untrusted deserialized JSON
var status = (Status)rawValue; // compiles and runs, but is INVALID
bool isValid = Enum.IsDefined(typeof(Status), status); // false -- correctly caught
Real-world example Validating a status or category field parsed from external API input before it's trusted and used in business logic.

Common follow-ups: Why doesn't Enum.IsDefined() work correctly out of the box for validating combined [Flags] enum values?

Exception Handling
A switch expression over an enum lets you handle each member concisely and return a value directly; the compiler emits a WARNING (not an error, unlike some other languages) if you don't handle every enum member and lack a discard pattern, helping catch forgotten cases when new members are added later.
public enum OrderStatus { Pending, Shipped, Delivered, Cancelled }

string Describe(OrderStatus status) => status switch {
  OrderStatus.Pending => "Waiting to ship",
  OrderStatus.Shipped => "On its way",
  OrderStatus.Delivered => "Arrived",
  OrderStatus.Cancelled => "Order cancelled",
  _ => throw new ArgumentOutOfRangeException(nameof(status)) // catches truly invalid/undefined values
};
Real-world example Mapping an OrderStatus enum to a user-facing display string, with the compiler helping catch forgotten cases as the enum evolves.

Common follow-ups: Why is it good practice to throw in the discard '_' case rather than silently returning a default value?

Records & Pattern Matching
Prior to more recent C# versions, you couldn't constrain a generic type parameter to 'must be an enum' cleanly enough to perform bitwise operations generically (the 'where T : struct, Enum' constraint exists, but bitwise operators aren't directly usable on a generic Enum-constrained parameter without casting through the underlying integral type) — this has led to workarounds using Convert.ToInt64() or reflection for fully generic flag utilities.
public static bool HasFlagGeneric<T>(T value, T flag) where T : struct, Enum {
  long valueAsLong = Convert.ToInt64(value);
  long flagAsLong = Convert.ToInt64(flag);
  return (valueAsLong & flagAsLong) == flagAsLong; // workaround: convert through long
}
Real-world example Writing a single, reusable flag-checking utility that works generically across MULTIPLE different [Flags] enum types in a codebase.

Common follow-ups: How do newer .NET generic math features (INumber<T> and related interfaces) begin to address this kind of generic numeric limitation?

Generics
Represent states as an enum, and define an explicit transition-validation method (often using a switch expression matching CURRENT state and DESIRED next state as a tuple) that only allows specific, intentional transitions — throwing or returning false for any transition not explicitly permitted, preventing invalid state changes from silently succeeding.
public enum OrderState { Created, Paid, Shipped, Cancelled }

public bool CanTransition(OrderState from, OrderState to) => (from, to) switch {
  (OrderState.Created, OrderState.Paid) => true,
  (OrderState.Created, OrderState.Cancelled) => true,
  (OrderState.Paid, OrderState.Shipped) => true,
  (OrderState.Paid, OrderState.Cancelled) => true,
  _ => false // every other transition is explicitly disallowed
};
Real-world example Enforcing a valid order lifecycle (Created -> Paid -> Shipped) and rejecting invalid transitions like Shipped -> Created.

Common follow-ups: How would you extend this design to also carry data specific to each state, beyond what a plain enum alone can represent?

Records & Pattern Matching