10 questions foundAn 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