Menu

C# Enum: Values, ToString, Parse, Flags and Iterating

How enums work in C#: declaring named constants, underlying integer values and casting, converting an enum to a string and a string to an enum with Parse and TryParse, listing all values, [Flags] with bitwise operators and HasFlag, switching on an enum, and handling undefined values.

This page includes runnable editors - edit, run, and see output instantly.

An enum (enumeration) is a type whose values are a fixed set of named constants: order statuses, days of the week, log levels. Underneath, each name is an integer, but the type system keeps an OrderStatus from being mixed up with a plain int or with another enum.

Declaring and using an enum

List the member names in braces. By default the first is 0 and each next one is one higher:

Output:

Paid
On its way
True
2

The enum is a real type: a method that takes an OrderStatus cannot be called with 3 or with a LogLevel by mistake. Enums are value types, so they are never null and compare with == by value.

Explicit values and the underlying type

You can assign numbers yourself. That matters whenever the number leaves your program (a database column, an HTTP status, a file format), because then renumbering breaks stored data:

Output:

404
Created
418
1
Byte

Two things to notice. Casting an int to an enum never fails: (HttpStatus)418 is a valid value that just has no name, and it prints as the number. And the underlying type can be any integral type (byte, short, long, ...), which only matters for storage-sensitive code; int is the default and the right choice almost always.

When you add members later, add them at the end or give explicit values. Inserting Refunded between Paid and Shipped silently changes the number of every member after it.

Enum to string

ToString() returns the member name, which is also what Console.WriteLine and string interpolation use. Format strings change the output:

Output:

Warning
2
00000002
[Warning]
Error
Error
Needs attention

Member names are identifiers, so they cannot contain spaces and they are not translated. For text shown to users, map the values yourself, as Label does, or with a Dictionary<LogLevel, string>. Some codebases put a [Description("Needs attention")] attribute on each member and read it with reflection; the reflection and attributes page shows how that lookup works.

String to enum: Parse and TryParse

Enum.Parse converts a name back into a value and throws ArgumentException if nothing matches. Enum.TryParse returns false instead, which is what you want for any input you do not control:

Output:

Large
Medium
Parse threw ArgumentException for Huge
small  parsed=True  value=Small  defined=True
XL     parsed=False value=Small  defined=True
2      parsed=True  value=Large  defined=True
7      parsed=True  value=7      defined=False

The last two rows are the trap. Both methods accept numeric strings, so "7" parses successfully into a Size that has no name. And a failed TryParse sets the result to 0, which here is the valid-looking Small. When the text comes from a query string, a config file or a form, always check both the return value and Enum.IsDefined:

if (Enum.TryParse(input, true, out Size size) && Enum.IsDefined(typeof(Size), size))
{
    // safe to use size
}

.NET Core 2.0 and later add a generic Enum.Parse<Size>("Large") that needs no cast.

Listing all values

Enum.GetValues returns every member, sorted by numeric value (compared as unsigned, so negative members come last); Enum.GetNames returns their names. This is how you fill a dropdown or validate against every option:

Output:

Free       0 EUR/month
Starter    9 EUR/month
Pro       29 EUR/month
Team      99 EUR/month
Free | Starter | Pro | Team
3 paid plans

Enum.GetValues(typeof(Plan)) returns a plain Array, hence the Cast<Plan>() before LINQ. On .NET 5 and later, Enum.GetValues<Plan>() returns a typed Plan[] directly.

Flags: combining values

Some enums describe a set of options rather than one choice: file permissions, days a shop is open, notification channels. Give each member its own bit (1, 2, 4, 8, ...), add None = 0, and mark the enum [Flags]. Values then combine with |:

Output:

Read, Share
Editor, Share
True
False
Editor
3
Read, Delete
True

What each operator does: | sets bits, & ~X clears them, ^ toggles them, and (value & X) != 0 or value.HasFlag(X) tests them. HasFlag(X) means "all of X's bits are set", so HasFlag(None) is true for every value, and HasFlag(Editor) requires both Read and Write.

Notice the second line: when a named combination covers some of the set bits, ToString uses it, so Read | Write | Share prints as Editor, Share. Keep that in mind before parsing the output of ToString with anything other than Enum.Parse.

The attribute does not change the arithmetic. It changes formatting: without [Flags], Read | Share prints as 9, because no single member has that value. With it, ToString and Parse both work with the comma-separated form. Members must still be powers of two; writing Read, Write, Delete with default numbering (0, 1, 2) makes Write | Delete equal 3, a meaningless value.

Switching on an enum

switch is the natural way to act on an enum. Include a default branch, because an enum variable can hold values with no name:

switch (status)
{
    case OrderStatus.Pending:
    case OrderStatus.Paid:
        return "Preparing";
    case OrderStatus.Shipped:
        return "On the way";
    case OrderStatus.Delivered:
        return "Delivered";
    default:
        return "Unknown";
}

Since C# 8 a switch expression is shorter. Without a _ arm the compiler warns: CS8509 when a named member is missing, and CS8524 when every name is handled but unnamed values such as (OrderStatus)7 are not:

string text = status switch
{
    OrderStatus.Pending or OrderStatus.Paid => "Preparing",   // 'or' pattern: C# 9
    OrderStatus.Shipped => "On the way",
    OrderStatus.Delivered => "Delivered",
    OrderStatus.Cancelled => "Cancelled",
    _ => throw new ArgumentOutOfRangeException(nameof(status)),
};

Default and undefined values

The default value of any enum is 0, whether or not a member has that value. Fields, array elements and a failed TryParse all produce it. Design for this:

  • Make 0 a meaningful "not set" member (None, Unknown) rather than a real choice. Otherwise an uninitialized field silently reads as the first real option.
  • Validate numbers from outside with Enum.IsDefined. For [Flags] enums, IsDefined returns false for combinations that have no name (Read | Share), so check the bits instead: (value & ~Permissions.All) == 0 with an All member covering every bit.

Common mistakes

  • Trusting TryParse alone. Numeric strings parse, and a failed parse yields 0. Add Enum.IsDefined.
  • Relying on implicit numbering for stored values. Inserting a member renumbers the ones after it. Assign explicit values to any enum that is persisted.
  • Flags without powers of two. Default numbering (0, 1, 2, 3) overlaps bits. Use 1, 2, 4, 8, or 1 << n.
  • Showing ToString() to users. Member names are code identifiers. Map values to display text.
  • No default in a switch. An enum can hold values outside its named members.

Frequently Asked Questions

How do I convert an enum to a string in C#?

Call ToString(): OrderStatus.Shipped.ToString() returns "Shipped", and string interpolation does the same. ToString("D") gives the number instead. For a name known at compile time, nameof(OrderStatus.Shipped) is a constant. For user-facing text with spaces or translations, map values to strings yourself (a switch or a dictionary) rather than relying on the member name.

How do I convert a string to an enum in C#?

Use Enum.TryParse<OrderStatus>(text, true, out var status), which returns false instead of throwing when the text matches no member (the true makes it case-insensitive). Enum.Parse(typeof(OrderStatus), text) throws ArgumentException on bad input. Both also accept numeric strings such as "42", so check the result with Enum.IsDefined when the input comes from users.

How do I convert between an enum and an int in C#?

Cast in either direction: int code = (int)OrderStatus.Paid; and var status = (OrderStatus)2;. The cast from int never fails, even for numbers with no matching member; the result is an enum value that prints as the number. Validate with Enum.IsDefined(typeof(OrderStatus), value) when the number comes from outside.

How do I loop through all values of an enum in C#?

foreach (OrderStatus s in Enum.GetValues(typeof(OrderStatus))) visits every member in order of their numeric values. Since .NET 5 there is a generic version, Enum.GetValues<OrderStatus>(), that needs no cast. Enum.GetNames(typeof(OrderStatus)) returns the names as strings.

What does [Flags] do on a C# enum?

It marks an enum whose values are bits meant to be combined with |, such as Read | Write. Give each member a power of two (1, 2, 4, 8) and a None = 0. The attribute makes ToString() print combinations as "Read, Write" and lets Enum.Parse read that format back. Test a bit with HasFlag or (value & Permissions.Write) != 0.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED