Enums
Reviewed & published by Brayan K
How do you represent "an order's status" or "a day of the week" in code? Magic numbers (status == 2) and loose strings ("shipped") are error-prone — a typo compiles fine and breaks at runtime. Enums give you a fixed, named set of options the compiler enforces, turning vague values into clear, typo-proof, self-documenting code.
Part of the free C# course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Declare an enum and use its named values
- Why enums beat magic numbers and loose strings
- Cast between an enum and its underlying int
- switch over an enum cleanly and safely
- Assign explicit values and parse names with TryParse
- Combine options with a [Flags] enum
💡 Real-World Analogy
An enum is like the fixed set of buttons on a microwave: Defrost, Reheat, Popcorn. You can only press one of the labelled options — there's no way to "press button 7.5" or invent a button that doesn't exist. That constraint is exactly what makes it safe and obvious. Compare that to writing the mode as free text on a sticky note ("reheet"?) — a typo nobody catches until the food is cold. Enums are the labelled buttons; strings and magic numbers are the smudged sticky note.
1. Declaring & Using an Enum
Define an enum with a name and a comma-separated list of members. Behind the scenes each member is an int starting at 0, but you mostly work with the names. Printing an enum shows the name, comparing is typo-proof, and you can cast to the underlying number whenever you need it.
using System;
// An enum is a named set of related constant values.
enum OrderStatus
{
Pending, // 0 (numbering starts at 0 by default)
Paid, // 1
Shipped, // 2
Delivered, // 3
Cancelled // 4
}
class Program
{
static void Main()
{
OrderStatus status = OrderStatus.Paid;
// Printing an enum shows its NAME, not its number — readable!
Console.WriteLine($"Status: {status}"); // Status: Paid
// You can still see the underlying int with a cast.
Console.WriteLine($"As number: {(int)status}"); // As number: 1
// Compare enums directly — clear and typo-proof.
if (status == OrderStatus.Paid)
Console.WriteLine("Payment received, preparing to ship.");
// Convert a number back to the enum:
OrderStatus fromNumber = (OrderStatus)3;
Console.WriteLine($"3 means: {fromNumber}"); // 3 means: Delivered
}
}
// ✅ Expected output:
// Status: Paid
// As number: 1
// Payment received, preparing to ship.
// 3 means: Delivered2. Switching on an Enum
Enums and switch are made for each other. A switch expression maps each enum value to a result in a compact, readable block, and the _ fallback keeps you safe. You can also enumerate every value with Enum.GetValues<T>() — handy for menus or validation.
using System;
enum TrafficLight { Red, Amber, Green }
class Program
{
static void Main()
{
TrafficLight light = TrafficLight.Amber;
// 'switch' over an enum reads beautifully and the compiler
// can even warn you if you forget a case.
string action = light switch
{
TrafficLight.Red => "Stop",
TrafficLight.Amber => "Prepare to stop",
TrafficLight.Green => "Go",
_ => "Unknown" // fallback for safety
};
Console.WriteLine($"{light}: {action}"); // Amber: Prepare to stop
// Loop over EVERY value of an enum with Enum.GetValues:
foreach (TrafficLight l in Enum.GetValues<TrafficLight>())
Console.WriteLine($" {(int)l} = {l}");
// 0 = Red
// 1 = Amber
// 2 = Green
}
}
// ✅ Expected output:
// Amber: Prepare to stop
// 0 = Red
// 1 = Amber
// 2 = GreenYour turn. Given a Day enum, work out whether the day is a weekend. The hint shows the exact condition.
using System;
enum Day { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday }
class Program
{
static void Main()
{
// 🎯 YOUR TURN — decide if a day is a weekend.
Day today = Day.Saturday;
// 1) Set isWeekend to true if today is Saturday OR Sunday.
bool isWeekend = ___; // 👉 today == Day.Saturday || today == Day.Sunday
// 2) Print the result with the day name.
Console.WriteLine($"{today} weekend? {isWeekend}");
// ✅ Expected output: Saturday weekend? True
}
}3. Explicit Values & Parsing
You don't have to accept the default 0, 1, 2 numbering. Assign explicit values — perfect for HTTP status codes or priority levels — and any members after a set value auto-increment from it. To go the other way, Enum.TryParse safely turns a name string back into an enum, exactly like int.TryParse.
using System;
// You can assign explicit values — handy for status codes or scores.
enum HttpStatus
{
OK = 200,
NotFound = 404,
ServerError = 500
}
// Subsequent members auto-increment from the last value you set.
enum Level
{
Low = 1,
Medium, // 2
High, // 3
Critical = 10
}
class Program
{
static void Main()
{
Console.WriteLine($"{(int)HttpStatus.NotFound} {HttpStatus.NotFound}");
// 404 NotFound
Console.WriteLine($"Medium = {(int)Level.Medium}"); // Medium = 2
Console.WriteLine($"Critical = {(int)Level.Critical}"); // Critical = 10
// Safe parsing of a name into an enum (like int.TryParse):
if (Enum.TryParse<Level>("High", out Level parsed))
Console.WriteLine($"Parsed: {parsed} = {(int)parsed}"); // Parsed: High = 3
}
}
// ✅ Expected output:
// 404 NotFound
// Medium = 2
// Critical = 10
// Parsed: High = 3🔎 Deep Dive: [Flags] enums
Sometimes you want several options at once — like read and write permissions. A [Flags] enum makes this clean: assign each member a power of two (1, 2, 4, 8) so each occupies its own bit, then combine them with the bitwise OR operator | and test with HasFlag. One variable now holds a whole set of options.
using System;
// [Flags] lets ONE value combine several options via bitwise OR.
// Use powers of two so each option is an independent bit.
[Flags]
enum Permissions
{
None = 0,
Read = 1,
Write = 2,
Execute = 4,
All = Read | Write | Execute // 7
}
class Program
{
static void Main()
{
// Combine flags with | (bitwise OR).
Permissions p = Permissions.Read | Permissions.Write;
Console.WriteLine($"Granted: {p}"); // Granted: Read, Write
// Test for a flag with HasFlag.
Console.WriteLine($"Can write? {p.HasFlag(Permissions.Write)}"); // True
Console.WriteLine($"Can execute? {p.HasFlag(Permissions.Execute)}"); // False
Console.WriteLine($"All = {(int)Permissions.All}"); // All = 7
}
}
// ✅ Expected output:
// Granted: Read, Write
// Can write? True
// Can execute? False
// All = 7The trick is the power-of-two values: Read=1, Write=2, Execute=4 have no overlapping bits, so OR-ing them produces a unique combined number (Read | Write = 3) that the runtime can still print as Read, Write.
🧩 Reorder Challenge
These lines declare a Size enum and print a message for a medium size. Order them so the program prints Selected: Medium (1).
Console.WriteLine($"Selected: {chosen} ({(int)chosen})");
enum Size { Small, Medium, Large }
Size chosen = Size.Medium;
// (enum is at file scope; the other two lines go inside Main)enum Size { Small, Medium, Large } // declared once, outside Main
// inside Main:
Size chosen = Size.Medium;
Console.WriteLine($"Selected: {chosen} ({(int)chosen})");Why: the enum type must be declared before it's used. Inside Main, chosen must be assigned before the WriteLine reads it. Medium is the second member, so its underlying value is 1, giving Selected: Medium (1).
🧠 Quick Recall
1. What does this print?
// enum Color { Red, Green, Blue }
Console.WriteLine((int)Color.Blue);2 — members number from 0, so Red=0, Green=1, Blue=2.
2. What is the output?
// enum Level { Low = 1, Mid, High }
Console.WriteLine((int)Level.High);3 — Low is explicitly 1, so Mid auto-increments to 2 and High to 3.
3. What prints here?
// [Flags] enum P { Read=1, Write=2, Exec=4 }
P p = P.Read | P.Exec;
Console.WriteLine(p.HasFlag(P.Write));False — only Read and Exec were combined, so the Write bit isn't set.
Pro Tips
- 💡 Replace magic numbers and status strings with an enum. The compiler then catches every typo.
- 💡 Always include a _ fallback in a switch over an enum for unexpected values.
- 💡 Use Enum.TryParse for user-supplied names — never Enum.Parse, which throws.
- 💡 For [Flags], use powers of two (1, 2, 4, 8) so each option owns its own bit.
Common Errors (and the fix)
- Casting an invalid number: (OrderStatus)99 compiles and gives a value with no name. Validate with Enum.IsDefined if the number is external.
- Enum.Parse throws on bad text: use Enum.TryParse for anything a user typed.
- Flags without powers of two: using 1, 2, 3 makes bits overlap and HasFlag misbehave. Use 1, 2, 4, 8.
- Comparing an enum to a raw number: works only after a cast. Prefer comparing to a named member for clarity.
Mini-Challenge: Turn-Right Simulator
Rotate a Direction enum with modulo arithmetic, then map the final direction to a compass arrow with a switch. The outline is in the comments.
using System;
enum Direction { North, East, South, West }
class Program
{
static void Main()
{
// 🎯 MINI-CHALLENGE: Turn-right simulator
// 1. Start facing: Direction facing = Direction.North;
// 2. Turning RIGHT goes North -> East -> South -> West -> North...
// Use the underlying int and modulo:
// facing = (Direction)(((int)facing + 1) % 4);
// 3. Turn right 3 times in a loop, printing the new direction each time.
// 4. Use a switch expression to print a compass arrow for the final
// direction (North = ↑, East = →, South = ↓, West = ←).
//
// ✅ Example output:
// Now facing East
// Now facing South
// Now facing West
// Final: West ←
// your code here
}
}🎉 Lesson Complete
- ✅ An enum is a named set of constants — readable and typo-proof
- ✅ Members are int from 0 by default; cast with (int) and (MyEnum)
- ✅ switch over an enum is clean; Enum.GetValues<T>() lists them all
- ✅ Assign explicit values; parse names safely with Enum.TryParse
- ✅ [Flags] with powers of two combines options via | and HasFlag
Practice quiz
What is an enum?
- A collection that grows dynamically
- A reference type with methods
- A named set of related constant values
- A kind of loop
Answer: A named set of related constant values. An enum is a named set of related constants, giving readable names the compiler checks for you instead of magic numbers or loose strings.
By default, what underlying type and starting value does an enum use?
- int, numbered from 0
- string, starting at "0"
- byte, numbered from 1
- long, numbered from -1
Answer: int, numbered from 0. By default an enum is backed by int, with members numbered 0, 1, 2... in order unless you assign explicit values.
In `enum Color { Red, Green, Blue }`, what is `(int)Color.Blue`?
- 0
- 1
- 3
- 2
Answer: 2. Members number from 0, so Red=0, Green=1, Blue=2.
In `enum Level { Low = 1, Mid, High }`, what is `(int)Level.High`?
- 1
- 3
- 2
- 0
Answer: 3. Low is explicitly 1, so Mid auto-increments to 2 and High to 3.
What does printing an enum value (e.g. Console.WriteLine(status)) show?
- Its member name
- Its underlying number
- An empty string
- Its memory address
Answer: Its member name. Printing an enum shows the member's name, not its number — that's what makes enums readable.
What does a [Flags] enum let one value do?
- Hold only one option
- Store a string
- Combine several options at once via bitwise OR
- Auto-sort its members
Answer: Combine several options at once via bitwise OR. A [Flags] enum lets a single value combine several options using the bitwise OR operator, so one variable holds many flags.
Why must [Flags] enum members use powers of two (1, 2, 4, 8)?
- To save memory
- So each option occupies its own independent bit
- Because the compiler requires it
- To make them sort correctly
Answer: So each option occupies its own independent bit. Powers of two give each option its own bit with no overlap, so OR-ing them produces a unique combined value and HasFlag works correctly.
Which method safely converts a string to an enum without throwing?
- Enum.Parse
- Convert.ToEnum
- int.Parse
- Enum.TryParse
Answer: Enum.TryParse. Enum.TryParse returns false on an invalid name instead of throwing, just like int.TryParse. Avoid Enum.Parse for user input.
Given `[Flags] enum P { Read=1, Write=2, Exec=4 }` and `P p = P.Read | P.Exec;`, what is `p.HasFlag(P.Write)`?
- True
- False
- Throws
- null
Answer: False. Only Read and Exec were combined, so the Write bit isn't set and HasFlag returns False.
Why include a `_` case in a switch over an enum?
- It's required syntax
- To list every member
- As a fallback for unexpected or invalid values
- To improve performance
Answer: As a fallback for unexpected or invalid values. The _ discard pattern is a safe fallback for values you didn't explicitly handle (e.g. an out-of-range cast).
Continue this course
- Previous: Properties & Auto-Properties
- Next: Checkpoint: C# Basics — Combine input, collections and strings in a build challenge — then a quiz
- Quick reference: C# cheat sheet
Frequently asked questions
What is an enum and why would I use one?
An enum is a named set of related constants — like the days of the week or order statuses. Instead of passing around magic numbers (0, 1, 2) or loose strings ("Pending", "Shipped"), you use readable names the compiler checks for you. This prevents typos, makes code self-documenting, and gives you autocomplete for every valid option.
What underlying type does an enum use, and can I change it?
By default an enum is backed by int, with members numbered 0, 1, 2 in order unless you assign explicit values. You can choose a different integer base, for example enum Size : byte, to save memory or match an external format. You can always cast an enum to and from its underlying number with an explicit cast.
What is a [Flags] enum?
A [Flags] enum lets a single value combine several options using bitwise OR, so one variable can hold many flags at once. You assign power-of-two values (1, 2, 4, 8) so each bit is independent, then combine with the | operator and test with HasFlag. It is ideal for things like file permissions where multiple options apply together.
How do I safely convert a string to an enum?
Use Enum.TryParse<MyEnum>(text, out var result), which returns false on an invalid name instead of throwing, just like int.TryParse. Pass true for the ignoreCase parameter if you want case-insensitive matching. Avoid Enum.Parse for user input because it throws on anything it does not recognise.