Enums
C# enums are typed labels for integral values. Combined with [Flags] for bitwise compositions, conversion helpers, and switch expressions, they cover status codes, permission masks, parsing, and any closed set you want compile-time safety on.
Definition, Flags, conversion, switch
EXAMPLE
// 1) Basic enum
public enum Direction { North, East, South, West }
Direction d = Direction.North;
d.ToString(); // 'North'
(int)d; // 0
// Underlying type — default is int; can specify
public enum Status : byte { Active = 1, Inactive = 0, Pending = 2 }
// 2) Switch expressions (exhaustive)
static string Describe(Direction d) => d switch
{
Direction.North => "up",
Direction.East => "right",
Direction.South => "down",
Direction.West => "left",
_ => "unknown",
};
// In C# 11+ with switch on enums, the compiler warns when cases are missing.
// 3) Enum with explicit values + parsing
public enum HttpStatus
{
OK = 200,
Created = 201,
NoContent = 204,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
InternalServerError = 500,
}
HttpStatus s = (HttpStatus)200;
bool valid = Enum.IsDefined(typeof(HttpStatus), 999); // false
if (Enum.TryParse<HttpStatus>("NotFound", out var parsed))
{
// ok
}
// 4) [Flags] for bitwise composition
[Flags]
public enum Permissions
{
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Delete = 1 << 2,
Admin = Read | Write | Delete, // composite
}
var p = Permissions.Read | Permissions.Write;
bool canRead = p.HasFlag(Permissions.Read); // true
bool canDelete = (p & Permissions.Delete) != 0;
Console.WriteLine(p); // 'Read, Write' (ToString respects [Flags])
// Add / remove flags
p |= Permissions.Delete; // add Delete
p &= ~Permissions.Write; // remove Write
// 5) Iterating values
foreach (var dir in Enum.GetValues<Direction>())
{
Console.WriteLine(dir);
}
string[] names = Enum.GetNames<Direction>(); // 'North', 'East', 'South', 'West'
// 6) Display names + descriptions
using System.ComponentModel;
using System.Reflection;
public enum OrderStatus
{
[Description("Cart")] Pending,
[Description("Awaiting payment")] AwaitingPayment,
[Description("Paid + ready")] Paid,
}
public static string GetDescription<T>(T value) where T : Enum
{
var member = typeof(T).GetMember(value.ToString());
var attr = member[0].GetCustomAttribute<DescriptionAttribute>();
return attr?.Description ?? value.ToString();
}
GetDescription(OrderStatus.AwaitingPayment); // 'Awaiting payment'
// 7) JSON — System.Text.Json string enum converter
using System.Text.Json.Serialization;
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Role { Guest, User, Admin }
// Or globally:
var options = new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } };
// 8) EF Core — store enum as string
modelBuilder.Entity<User>()
.Property(u => u.Role)
.HasConversion<string>();
// Storing as string survives reordering values; storing as int can shift if you reorder.
// 9) Generic constraint where T : Enum
public static T Random<T>() where T : struct, Enum
{
var values = Enum.GetValues<T>();
return values[new Random().Next(values.Length)];
}
var randomDir = Random<Direction>();
// 10) Validate user input
public static Direction ParseDirection(string input)
{
if (Enum.TryParse<Direction>(input, true, out var d) && Enum.IsDefined(d))
return d;
throw new ArgumentException($"Invalid direction: {input}");
}
// 11) Casting safely
int raw = 42;
if (Enum.IsDefined(typeof(HttpStatus), raw))
{
var status = (HttpStatus)raw; // OK to cast
}
else
{
// unknown
}
// 12) Performance — HasFlag vs bitwise AND
// HasFlag boxes the enum in older runtimes (.NET Framework). In modern .NET (Core+), it's optimised.
// Bitwise AND is still slightly faster + zero-allocation:
if ((p & Permissions.Read) == Permissions.Read) { /* … */ }
// 13) Enum classes (struct alternative)
// For richer behaviour, use sealed records + a static class as an enum alternative
public sealed record StateMachineState
{
public string Name { get; }
public string DisplayName { get; }
private StateMachineState(string n, string d) { Name = n; DisplayName = d; }
public static readonly StateMachineState Pending = new('pending', 'In review');
public static readonly StateMachineState Approved = new('approved', 'Live');
public static readonly StateMachineState Rejected = new('rejected', 'Closed');
}
// Useful when you need fields beyond a single integral.
// 14) Common bugs
// • Storing enum as int in DB then reordering values → data shifts; store as STRING (HasConversion<string>())
// • Forgetting [Flags] but using bitwise → ToString shows numeric value, debugging confusion
// • IsDefined inside hot loops → reflection cost; cache HashSet of valid values
// • Casting unrelated ints to enum without IsDefined check — invalid enum values in your code
// • Switch on enum without a default and missing cases → compiler warns; embrace it
// • Equals between unrelated enums — compile error (good); cast to int if you really mean to
// • Default(MyEnum) = 0; if 0 isn't a real value, you get an invalid state — always include 'None = 0' or similar
// • JSON deserialisation of unknown string → throws; configure to accept names + fallback default
Why it matters
C# enums earn their keep with [Flags] for permission masks, switch expressions for exhaustive matching, and HasConversion<string>() in EF Core so you can safely reorder values later. Always include a None = 0 entry, validate untrusted input with Enum.IsDefined, and reach for record-based “smart enums” when you need fields beyond a single integral value.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
enum Day { Mon, Tue, Wed, Thu, Fri, Sat, Sun }
Day d = Day.Fri;
if (d == Day.Fri) Console.WriteLine("🎉");
Try it Yourself »
Discussion
Loading…