How to Use Enum in C#: Declaration, Values, and Best Practices
Magic numbers are a silent tax on every codebase that uses them. When you see if (role == 3) six months after writing it, you have to trace back through history to remember what 3 means. Using enum in C# eliminates that problem entirely -- it replaces numeric literals with meaningful names that read like prose.
This guide covers how to declare enums, assign explicit values, cast to and from integers, compare and iterate over members, and the best practices that separate clean enum usage from the messy patterns that sneak into production.
Declaring an Enum in C#
Declaring an enum is one line of boilerplate and as many members as your domain requires:
public enum OrderStatus
{
Pending,
Processing,
Shipped,
Delivered,
Cancelled
}
The public modifier makes the enum available across your project. You can also use internal (assembly-scoped), private (nested inside a class), or protected (in a base class).
By default, enum members are automatically numbered starting at 0. Pending is 0, Processing is 1, and so on. This implicit numbering is fine when the values are only ever used within your code. When values are persisted to a database or shared with an external system, explicit assignment is safer -- covered in the next section.
Where to declare enums: Enums are types, so they live at the namespace level (same file as the types that use them, or their own file). Avoid declaring enums inside a method -- that limits their visibility unnecessarily.
Assigning Explicit Values
When you need to control what integer each member maps to, assign values explicitly:
public enum HttpStatusCode
{
Ok = 200,
Created = 201,
NoContent = 204,
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404,
InternalServerError = 500
}
Explicit values are important when:
- The enum represents a protocol or external system that uses fixed numeric codes
- The values are stored in a database and new members may be added later
- You want to future-proof the enum so reordering members does not silently change stored data
A critical rule: never rely on implicit ordering for persisted enums. If you have enum Role { Viewer, Editor, Admin } and later insert Moderator between Editor and Admin, all stored Admin values become Moderator values without any compile-time warning.
Using an Enum Variable
Once declared, using an enum in C# is straightforward:
// Declare and assign
OrderStatus status = OrderStatus.Pending;
// Compare
if (status == OrderStatus.Pending)
{
Console.WriteLine("Order has not started yet.");
}
// Assign to a field
public class Order
{
public OrderStatus Status { get; set; } = OrderStatus.Pending;
}
// Pass to a method
void ProcessOrder(Order order, OrderStatus targetStatus)
{
if (order.Status != targetStatus)
{
// ...
}
}
The compiler enforces that you use only named members -- OrderStatus.Pending, not 5. This type safety prevents an entire class of bugs.
Casting Between Enum and int
Sometimes you need to convert an enum to its underlying integer (for storage, comparison with external data) or parse an integer back into an enum:
OrderStatus status = OrderStatus.Shipped;
// Enum to int -- explicit cast required
int statusInt = (int)status; // 2
// int to enum -- also explicit cast
OrderStatus fromInt = (OrderStatus)2; // Shipped
// Dangerous: C# does NOT validate the cast
OrderStatus invalid = (OrderStatus)99; // No exception! Value 99 has no name.
Console.WriteLine(invalid); // "99" -- not a member name
The cast from int to enum does not throw, even for undefined values. Always validate external integers before casting:
int incoming = GetStatusFromExternalApi();
if (!Enum.IsDefined(typeof(OrderStatus), incoming))
{
throw new ArgumentException($"Unknown order status: {incoming}");
}
OrderStatus status = (OrderStatus)incoming;
Or use Enum.TryParse for string-based inputs from APIs or user forms:
if (Enum.TryParse<OrderStatus>(userInput, ignoreCase: true, out OrderStatus parsed))
{
// Use parsed safely
}
else
{
// Handle invalid input
}
Comparing Enum Values
C# enum members support equality, inequality, and relational comparison:
OrderStatus a = OrderStatus.Shipped;
OrderStatus b = OrderStatus.Delivered;
bool equal = (a == b); // false
bool notEq = (a != b); // true
// Relational comparison (based on underlying int value)
bool shipped = (a < b); // true -- Shipped (2) < Delivered (3)
Relational comparison is meaningful for ordered enums like severity levels or priority rankings, but less useful for enums without inherent ordering. Equality comparison is the common case.
For switch-based branching, use the switch expression for clean, exhaustive handling:
string GetLabel(OrderStatus status) => status switch
{
OrderStatus.Pending => "Pending",
OrderStatus.Processing => "Processing",
OrderStatus.Shipped => "Shipped",
OrderStatus.Delivered => "Delivered",
OrderStatus.Cancelled => "Cancelled",
_ => "Unknown"
};
For an in-depth look at switch patterns with C# enum, see the C# Enum Switch Pattern Matching guide.
Iterating Over All Enum Values
Two patterns cover most iteration needs:
// Iterate over all defined members (.NET 5+)
foreach (OrderStatus status in Enum.GetValues<OrderStatus>())
{
Console.WriteLine($"{status} = {(int)status}");
}
// Output:
// Pending = 0
// Processing = 1
// Shipped = 2
// Delivered = 3
// Cancelled = 4
// Get all member names
string[] names = Enum.GetNames<OrderStatus>();
// Older syntax for .NET Framework or early .NET Core
foreach (OrderStatus status in (OrderStatus[])Enum.GetValues(typeof(OrderStatus)))
{
// ...
}
Iteration is useful for:
- Populating dropdown lists or selection controls
- Generating string-to-enum lookup tables
- Writing exhaustive unit tests that cover every enum member
- Logging all valid values when validating input
Enum in Class Properties and Method Parameters
Enums make APIs self-documenting. Compare these two method signatures:
// Bad -- what does int status mean?
void UpdateOrder(int orderId, int status) { }
// Good -- enum makes intent explicit
void UpdateOrder(int orderId, OrderStatus status) { }
The second version documents its contract at the type level. Callers know exactly what values are valid without consulting documentation.
In a domain model, use enum properties alongside nullable types when the value is optional:
public class Shipment
{
public Guid Id { get; init; }
public OrderStatus Status { get; set; } = OrderStatus.Pending;
public DateTimeOffset? ShippedAt { get; set; }
public DateTimeOffset? DeliveredAt { get; set; }
}
For a comprehensive look at how enums fit into the broader C# type system, see C# Enum: Complete Guide to Enumerations.
Common Mistakes When Using C# Enum
Not defining a zero member. An uninitialized enum field defaults to 0. If no member maps to 0, you have a valid variable holding an unnamed value:
// Problematic -- default(OrderStatus) is 0, but no member = 0
public enum OrderStatus
{
Pending = 1,
Shipped = 2
}
// Safe -- 0 is explicitly represented
public enum OrderStatus
{
None = 0,
Pending = 1,
Shipped = 2
}
Casting integers without validation. As shown above, (OrderStatus)99 compiles and runs without error. Always validate external data before casting.
Adding members without updating all switch statements. If you add OnHold to OrderStatus, every switch that lacks a _ arm for that member will silently fall through. Use the throw-on-unknown pattern in your discard arm.
Using enum to represent open-ended categories. If new values can appear at runtime (user-defined tags, plug-in type names), use a string or a value object. Enums are for closed, known sets defined at compile time.
For more patterns to avoid, How to Use Enums in CSharp -- Understanding the Basics and You're Using Enums Wrong cover real-world anti-patterns.
Enum Best Practices Summary
Here is a quick reference checklist:
- Always define a zero-value member (
None,Unknown, or a domain-appropriate default) - Use explicit values when the enum is persisted or shared with external systems
- Validate
int-to-enum casts usingEnum.IsDefinedbefore casting - Use switch expressions with a throw-on-unknown discard arm
- Keep enum names singular (
OrderStatus, notOrderStatuses) - Avoid modifying existing member values -- it silently breaks stored data
- Prefer separate smaller enums over one large multi-purpose enum
Frequently Asked Questions
How do I declare an enum in C#?
Use the enum keyword followed by the name and a block of member names. By default, members start at 0 and increment by one. Assign explicit integer values after = when you need specific numbering.
What is the default value of an enum in C#?
The default value is always 0. If you do not define a member with value 0, an uninitialized enum variable silently holds an undefined value. Always define a named zero member to avoid this.
How do I convert an enum to an integer in C#?
Cast explicitly: int value = (int)myEnum;. The cast never throws. To convert back, use (MyEnum)intValue but validate first with Enum.IsDefined to ensure the integer maps to a named member.
Can I compare enum values in C#?
Yes, enum values support ==, !=, <, >, <=, and >=. Equality comparison is the most common. Relational comparison is based on the underlying integer order and is rarely meaningful in domain logic.
How do I iterate over all enum members in C#?
Use Enum.GetValues<T>() in .NET 5+ or (T[])Enum.GetValues(typeof(T)) in earlier runtimes. This returns every defined member in declaration order.
How do I use an enum in a switch statement?
Write switch (myEnum) { case MyEnum.Value: ... } for the classic syntax, or use the switch expression myEnum switch { MyEnum.Value => ..., _ => ... } for the modern concise form. The switch expression is preferred in .NET 6+.
What is the difference between enum and int in C#?
An int is just a number with no domain meaning. An enum wraps integers with a named type, restricting valid values to the defined members and making code self-documenting. The compiler enforces type safety -- you cannot pass an int where an enum is expected without an explicit cast.

