Enums
An enum (enumeration) is a type with a fixed set of named values: days of the week, order statuses, log levels, directions. Using an enum instead of magic numbers or strings makes code readable, lets the compiler catch typos, and lets your editor suggest the valid options.
Declaring and using an enum
OrderStatus status = OrderStatus.Pending; Console.WriteLine(status); status = OrderStatus.Shipped; if (status == OrderStatus.Shipped) { Console.WriteLine("On its way!"); } enum OrderStatus { Pending, Paid, Shipped, Delivered, Cancelled, }
Pending On its way!
Compare with storing the status as a string: status == "shiped" compiles happily and silently never matches. OrderStatus.Shiped is a compile error.
Underlying values
Each enum member is backed by an integer. By default the first is 0 and each next one is 1 higher, but you can choose the values. Casting converts between an enum and its number:
Console.WriteLine((int)Priority.Low); Console.WriteLine((int)Priority.Critical); Console.WriteLine((Priority)20); Console.WriteLine((Priority)7); // no such member: just the number Console.WriteLine(Enum.IsDefined((Priority)7)); Console.WriteLine(Priority.High > Priority.Medium); enum Priority { Low = 1, Medium = 10, High = 20, Critical = 100, }
1 100 High 7 False True
Note that casting an arbitrary number to an enum always succeeds, even if no member has that value — check with Enum.IsDefined when the number comes from outside your program. Always give explicit values if they're stored in a database or file, so reordering the members later doesn't change the meaning of existing data.
Enums with switch
Enums and switch expressions are a natural pair. The compiler even warns if you don't handle every member:
foreach (TrafficLight light in Enum.GetValues<TrafficLight>()) { Console.WriteLine($"{light,-6} -> {Action(light)}"); } static string Action(TrafficLight light) => light switch { TrafficLight.Red => "stop", TrafficLight.Amber => "get ready", TrafficLight.Green => "go", _ => throw new ArgumentOutOfRangeException(nameof(light)), }; enum TrafficLight { Red, Amber, Green }
Red -> stop Amber -> get ready Green -> go
Enum.GetValues<T>() returns every member of an enum. The _ arm handles numbers that aren't valid members.
Converting from text
Enum.Parse and Enum.TryParse turn a name into a member — handy for configuration files and user input. Pass ignoreCase: true to accept any capitalisation:
foreach (string input in new[] { "Saturday", "monday", "Funday" }) { if (Enum.TryParse(input, ignoreCase: true, out DayOfWeek day)) Console.WriteLine($"{input} -> {day} ({(int)day})"); else Console.WriteLine($"{input} -> not a day"); } Console.WriteLine(string.Join(", ", Enum.GetNames<DayOfWeek>()));
Saturday -> Saturday (6) monday -> Monday (1) Funday -> not a day Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
DayOfWeek is one of many enums built into .NET; others include ConsoleColor, StringComparison and FileMode.
Flags: combining values
Sometimes several options can apply at once — read and write permission, bold and italic text. Give each member its own bit (1, 2, 4, 8 …) and add the [Flags] attribute. Combine with | and test with HasFlag or &:
Permissions user = Permissions.Read | Permissions.Write; Console.WriteLine(user); Console.WriteLine(user.HasFlag(Permissions.Write)); Console.WriteLine(user.HasFlag(Permissions.Delete)); user |= Permissions.Delete; // add a flag user &= ~Permissions.Write; // remove a flag Console.WriteLine(user); Console.WriteLine(Permissions.All); Console.WriteLine((int)Permissions.All); [Flags] enum Permissions { None = 0, Read = 1, Write = 2, Delete = 4, Share = 8, All = Read | Write | Delete | Share, }
Read, Write True False Read, Delete All 15
The [Flags] attribute is what makes ToString print Read, Write instead of the number 3. Writing the values in binary (0b0001, 0b0010 …) or as shifts (1 << 2) makes the bit pattern clear.
Enums are value types
An enum variable is just an integer underneath, so it's cheap to store and compare. Its default value is whatever member equals 0 — which is why it's good practice to make 0 a sensible default such as None or Unknown:
var task = new TaskItem(); Console.WriteLine(task.Status); class TaskItem { public TaskStatus Status { get; set; } } enum TaskStatus { NotStarted, InProgress, Done }
NotStarted
Try it yourself
Use an enum for the size of a coffee and a switch expression for its price. Then parse an order from text.
Small: $2.40 Large: $3.40 Medium: $2.90 Unknown size 'huge'