Skip to main content

Enums

These methods work on enum values: reading the [Description] text you show to users, reading any other attribute on a member, and doing [Flags] arithmetic without casts. Reach for them when an enum drives labels in a UI, a report or an API response, or when you combine and test permission-style flags.

All of them live in the System namespace, so you need no extra using. They work with every underlying type, including signed enums with negative members and ulong enums that use the top bit.

At a glance​

MethodWhat it does
GetDescriptionReturns the member's [Description] text or its name; joins the parts of a flags combination with ", ".
GetAttribute<TAttribute>Returns any attribute applied to the enum member, or null.
GetFlags<T>Splits a [Flags] value into the single-bit members it contains.
IsDefined<T>Checks that a value is a declared member, accepting combinations of declared flags.
WithFlag<T>Returns the value with the given flag bits set.
WithoutFlag<T>Returns the value with the given flag bits cleared.
HasAnyFlag<T>Checks that at least one of the given flag bits is set.

The examples on this page use these two enums:

Enums used in the examples
using System.ComponentModel;

public enum OrderStatus
{
[Description("Waiting for payment")]
Pending,

Shipped,
}

[Flags]
public enum Permissions
{
None = 0,

[Description("Can read")]
Read = 1,

Write = 2,

[Description("Can execute")]
Execute = 4,

[Description("Read and write")]
ReadWrite = Read | Write,
}

Member metadata​

GetDescription​

GetDescription() returns the text of the member's [Description] attribute. When the member has no description, it returns the member name. Use it for labels in dropdowns, e-mails and exports, so the display text lives next to the enum member instead of in a switch.

Showing an order status to a customer
string pending = OrderStatus.Pending.GetDescription(); // → "Waiting for payment"
string shipped = OrderStatus.Shipped.GetDescription(); // → "Shipped"

For a [Flags] value that is not itself a named member, the method describes each single-bit flag and joins the parts with ", ". When the combination has its own named member, that member's description wins.

Describing a user's permissions
Permissions all = Permissions.Read | Permissions.Write | Permissions.Execute;
string summary = all.GetDescription(); // → "Can read, Write, Can execute"

string named = (Permissions.Read | Permissions.Write).GetDescription(); // → "Read and write"
string none = Permissions.None.GetDescription(); // → "None"

Values that are not made of declared members fall back to the text Enum.ToString() gives, which for these values is the number:

Undefined values
string unknownStatus = ((OrderStatus)99).GetDescription(); // → "99"
string unknownFlag = ((Permissions)8).GetDescription(); // → "8"
  • A null receiver (for example an Enum variable that holds null) throws ArgumentNullException with the parameter name value.
  • Only a value made entirely of single-bit members is described part by part. Otherwise (a set bit has no single-bit member, or the enum has no [Flags] attribute) you get the Enum.ToString() text: the number when some bit has no member at all, but in a [Flags] enum a list of member names, without their [Description] text, when a multi-bit or negative member covers the remaining bits.
  • The description text is returned as written. Nothing is translated or re-cased.
note

In a combination, each part is described on its own. Read | Write | Execute lists Read and Write separately; it does not use the ReadWrite description, because ReadWrite is not a single bit.

GetAttribute<TAttribute>​

GetAttribute<TAttribute>() returns the attribute of type TAttribute applied to the member that the value names, or null when there is none. Use it for metadata beyond a description, such as a colour, a sort key or an external code.

Reading a badge colour for an invoice state
[AttributeUsage(AttributeTargets.Field)]
public sealed class BadgeColorAttribute : Attribute
{
public BadgeColorAttribute(string hex) => Hex = hex;

public string Hex { get; }
}

public enum InvoiceState
{
[BadgeColor("#9e9e9e")]
Draft,

[BadgeColor("#2e7d32")]
Paid,

Void,
}

string paidColor = InvoiceState.Paid.GetAttribute<BadgeColorAttribute>()?.Hex ?? "#000000"; // → "#2e7d32"
string voidColor = InvoiceState.Void.GetAttribute<BadgeColorAttribute>()?.Hex ?? "#000000"; // → "#000000"

It works with built-in attributes too:

Reading the Description attribute directly
DescriptionAttribute? pending = OrderStatus.Pending.GetAttribute<DescriptionAttribute>();
string? text = pending?.Description; // → "Waiting for payment"

DescriptionAttribute? shipped = OrderStatus.Shipped.GetAttribute<DescriptionAttribute>(); // → null

The result is null when:

  • the member has no attribute of that type;
  • the value is not a declared member, such as (OrderStatus)42;
  • the value is a combination of flags that has no named member, such as Permissions.Read | Permissions.Execute.

A combination that is declared, such as Permissions.ReadWrite, returns that member's attribute. A null receiver throws ArgumentNullException with the parameter name value.

tip

For attributes on types, properties and methods, use GetAttribute<T>() on MemberInfo. See Types and reflection.

Flags​

GetFlags<T>​

GetFlags<T>() splits a [Flags] value into the single-bit members that are set. Use it to render one checkbox or chip per flag, or to store each flag as its own row.

Listing the flags a user has
Permissions granted = Permissions.Execute | Permissions.Read;
IReadOnlyList<Permissions> flags = granted.GetFlags(); // → [Permissions.Read, Permissions.Execute]

IReadOnlyList<Permissions> pair = Permissions.ReadWrite.GetFlags(); // → [Permissions.Read, Permissions.Write]
IReadOnlyList<Permissions> empty = Permissions.None.GetFlags(); // → []
  • Only single-bit members are returned. Named combinations such as ReadWrite and the zero member (None) are never in the list.
  • The members come back in order of their numeric value, whatever order you combined them in.
  • Bits that have no declared member are ignored.
  • If two members are declared with the same single-bit value (aliases), that bit appears once per alias. GetDescription repeats it the same way, but every copy uses the one name that ToString() picks for that value, so a [Description] on the other alias can be ignored.
  • It works for signed enums with negative members and for ulong enums with the top bit set. A negative member of an sbyte, short or int enum is never returned, not even the sign bit (such as a member set to int.MinValue), so for a combination that includes one, GetDescription falls back to the Enum.ToString() names and ignores their [Description] text, and IsDefined returns false. A long enum is the exception: a member equal to long.MinValue is returned, and it comes last. Use a uint, long or ulong enum when you need the top bit as a flag.

IsDefined<T>​

IsDefined<T>() checks that a value is a declared member of its enum. For a [Flags] enum it also accepts any combination whose bits are all declared single-bit members. Use it to validate a number that came from a request, a file or a database before you trust it.

Validating values from an API request
bool pending = OrderStatus.Pending.IsDefined(); // → true
bool unknown = ((OrderStatus)42).IsDefined(); // → false

bool combo = (Permissions.Read | Permissions.Execute).IsDefined(); // → true
bool all = ((Permissions)7).IsDefined(); // → true
bool extraBit = ((Permissions)(1 | 8)).IsDefined(); // → false
bool zero = Permissions.None.IsDefined(); // → true

Zero counts only when a member is declared for it:

Flags without a zero member
[Flags]
public enum NotificationChannels
{
Email = 1,
Sms = 2,
}

bool nothing = ((NotificationChannels)0).IsDefined(); // → false
Compared with Enum.IsDefined

Enum.IsDefined only accepts values that match one member exactly, so Enum.IsDefined(typeof(Permissions), Permissions.Read | Permissions.Execute) is false. This extension returns true for that value. For an enum without [Flags], both behave the same.

WithFlag<T>​

WithFlag<T>(T flag) returns the value with the bits of flag set. It is the same as value | flag, without the casts you need in generic code. The original variable is not changed, because enums are values.

Granting a permission
Permissions current = Permissions.Read;
Permissions updated = current.WithFlag(Permissions.Write); // → Permissions.ReadWrite

Permissions same = Permissions.ReadWrite.WithFlag(Permissions.Read); // → Permissions.ReadWrite
Permissions many = Permissions.None.WithFlag(Permissions.ReadWrite); // → Permissions.ReadWrite
  • flag can hold several bits; all of them are set.
  • Setting a bit that is already set returns the same value.
  • It works with signed and ulong enums. On a ulong enum it can set the top bit.

WithoutFlag<T>​

WithoutFlag<T>(T flag) returns the value with the bits of flag cleared. It is the same as value & ~flag.

Revoking a permission
Permissions current = Permissions.ReadWrite;
Permissions readOnly = current.WithoutFlag(Permissions.Write); // → Permissions.Read

Permissions unchanged = Permissions.Read.WithoutFlag(Permissions.Execute); // → Permissions.Read

Permissions all = Permissions.Read | Permissions.Write | Permissions.Execute;
Permissions executeOnly = all.WithoutFlag(Permissions.ReadWrite); // → Permissions.Execute
  • flag can hold several bits; all of them are cleared.
  • Clearing a bit that is not set returns the same value.

HasAnyFlag<T>​

HasAnyFlag<T>(T flags) returns true when the value and flags share at least one bit. Enum.HasFlag needs all the bits, so use this method for "can the user do any of these?" checks.

Checking access to an editor
Permissions user = Permissions.Read;

bool canOpen = user.HasAnyFlag(Permissions.ReadWrite); // → true
bool canEdit = user.HasFlag(Permissions.ReadWrite); // → false (Enum.HasFlag needs both bits)

bool overlap = Permissions.ReadWrite.HasAnyFlag(Permissions.Write | Permissions.Execute); // → true
bool noOverlap = Permissions.Execute.HasAnyFlag(Permissions.ReadWrite); // → false
Zero never matches

HasAnyFlag(Permissions.None) is always false, even when the value is also None. Enum.HasFlag(Permissions.None) is always true. Do not pass the zero member as the flags to look for.

Putting it together​

A user-management screen that adds a permission, lists the user's permissions and checks access:

Permission summary for an admin screen
Permissions granted = Permissions.Read;
granted = granted.WithFlag(Permissions.Execute);

IReadOnlyList<Permissions> chips = granted.GetFlags(); // → [Permissions.Read, Permissions.Execute]
List<string> labels = chips.Select(flag => flag.GetDescription()).ToList(); // → ["Can read", "Can execute"]
string summary = granted.GetDescription(); // → "Can read, Can execute"
bool canChangeFiles = granted.HasAnyFlag(Permissions.ReadWrite); // → true
bool isValid = granted.IsDefined(); // → true

Parsing text into enums​

To go from text to an enum, use the string extensions:

  • ToEnum<T> parses a name or number with Enum.Parse and throws on failure.
  • TryParse<T> returns null instead of throwing and accepts declared members (and combinations of them for [Flags] enums) only, ignoring case.
  • ToClosestEnum<T> finds the member whose name or [Description] text is closest to a typo, ignoring case.
From text to OrderStatus
OrderStatus shipped = "Shipped".ToEnum<OrderStatus>(); // → OrderStatus.Shipped
OrderStatus? typo = "pendng".ToClosestEnum<OrderStatus>(); // → OrderStatus.Pending

See also​