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
| Method | What it does |
|---|---|
GetDescription | Returns 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:
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.
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.
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:
string unknownStatus = ((OrderStatus)99).GetDescription(); // → "99"
string unknownFlag = ((Permissions)8).GetDescription(); // → "8"
- A
nullreceiver (for example anEnumvariable that holdsnull) throwsArgumentNullExceptionwith the parameter namevalue. - 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 theEnum.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.
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.
[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:
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.
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.
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
ReadWriteand 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.
GetDescriptionrepeats it the same way, but every copy uses the one name thatToString()picks for that value, so a[Description]on the other alias can be ignored. - It works for signed enums with negative members and for
ulongenums with the top bit set. A negative member of ansbyte,shortorintenum is never returned, not even the sign bit (such as a member set toint.MinValue), so for a combination that includes one,GetDescriptionfalls back to theEnum.ToString()names and ignores their[Description]text, andIsDefinedreturnsfalse. Alongenum is the exception: a member equal tolong.MinValueis returned, and it comes last. Use auint,longorulongenum 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.
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]
public enum NotificationChannels
{
Email = 1,
Sms = 2,
}
bool nothing = ((NotificationChannels)0).IsDefined(); // → false
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.
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
flagcan hold several bits; all of them are set.- Setting a bit that is already set returns the same value.
- It works with signed and
ulongenums. On aulongenum 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.
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
flagcan 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.
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
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:
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 withEnum.Parseand throws on failure.TryParse<T>returnsnullinstead 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.
OrderStatus shipped = "Shipped".ToEnum<OrderStatus>(); // → OrderStatus.Shipped
OrderStatus? typo = "pendng".ToClosestEnum<OrderStatus>(); // → OrderStatus.Pending
See also
- Parsing —
ToEnum<T>andTryParse<T>. - Fuzzy matching and natural sort —
ToClosestEnum<T>for "did you mean?" input. - Types and reflection — attributes on types and members.
- Random values —
NextEnum<T>()picks a random declared member.