Brainy.Enums
1.0.1
dotnet add package Brainy.Enums --version 1.0.1
NuGet\Install-Package Brainy.Enums -Version 1.0.1
<PackageReference Include="Brainy.Enums" Version="1.0.1" />
<PackageVersion Include="Brainy.Enums" Version="1.0.1" />
<PackageReference Include="Brainy.Enums" />
paket add Brainy.Enums --version 1.0.1
#r "nuget: Brainy.Enums, 1.0.1"
#:package Brainy.Enums@1.0.1
#addin nuget:?package=Brainy.Enums&version=1.0.1
#tool nuget:?package=Brainy.Enums&version=1.0.1
Brainy.Enums
A small, dependency-free toolkit of enum operations for C# / .NET 8 — conversions, validation, flags helpers, attribute lookups, and listing utilities — all exposed as simple extension methods.
Reflection is only ever performed once per enum type and cached internally, so every method is fast after the first call.
Table of Contents
- Install
- Quick Start
- Number ⇄ Enum
- Name (string) ⇄ Enum
- Description / Display Name
- Validation
- Ordering (Next / Previous / Range)
- Custom Attributes
- Listing & Metadata (
EnumInfo<T>) - Flags Enums
- Exception Handling
- Building from Source
- Design Notes
- Contributing
- License
Install
dotnet add package Brainy.Enums
Or via the Package Manager Console:
Install-Package Brainy.Enums
Quick Start
using Brainy.Enums;
using System.ComponentModel;
public enum Status
{
[Description("Pending Review")]
Pending = 0,
[Description("Currently Active")]
Active = 1,
Inactive = 2,
Archived = 3
}
Status s = 1.ToEnum<Status>(); // Status.Active
string d = s.ToDescription(); // "Currently Active"
Everything below assumes using Brainy.Enums; is at the top of the file.
Number ⇄ Enum
Convert between an enum and its underlying numeric value.
int n = Status.Active.ToInt32(); // 1
long l = Status.Active.ToInt64(); // 1
byte b = Status.Active.ToByte(); // 1
Status s1 = 1.ToEnum<Status>(); // Status.Active
Status s2 = 1L.ToEnum<Status>(); // Status.Active (long overload)
ToEnum<T>() throws EnumConversionException if the number doesn't map to a defined member.
Use the Try... form when you don't want an exception on invalid input:
if (99.TryToEnum<Status>(out var status))
{
Console.WriteLine($"Parsed: {status}");
}
else
{
Console.WriteLine("99 is not a valid Status.");
}
Note: for
[Flags]enums, any numeric value is accepted (since valid flag combinations aren't individually "defined" members) — see Flags Enums for stricter validation of flag combinations specifically.
Name (string) ⇄ Enum
Convert between an enum and its declared member name.
string name = Status.Active.ToName(); // "Active"
Status s1 = "Active".ToEnum<Status>(); // Status.Active
Status s2 = "active".ToEnum<Status>(ignoreCase: true); // Status.Active
Throws EnumConversionException for unknown names; use TryToEnum to avoid that:
bool ok = "Bogus".TryToEnum<Status>(out var status, ignoreCase: true);
// ok == false, status == default
Description / Display Name
Reads [Description("...")] (or [Display(Name = "...")] / [DisplayName("...")]) attributes
on enum members — handy for showing friendly text in UI instead of the raw member name.
string desc = Status.Active.ToDescription(); // "Currently Active"
string desc2 = Status.Archived.ToDescription(); // "Archived" (falls back to member name — no attribute needed)
Status s = "Pending Review".DescriptionToEnum<Status>(); // Status.Pending
ToDescription() never throws — if no [Description] attribute is present, it falls back
to the member name, so it's always safe to call directly in UI-binding code.
ToDisplayName() is the equivalent for [Display] / [DisplayName], and returns null
(not a fallback) if the attribute is absent:
string? display = Status.Active.ToDisplayName(); // null unless [Display]/[DisplayName] is present
Validation
bool defined = ((Status)42).IsDefined(); // false
bool defined2 = Status.Active.IsDefined(); // true
Ordering (Next / Previous / Range)
Move through an enum's defined members in declaration order.
Status next = Status.Active.Next(); // Inactive
Status prev = Status.Active.Previous(); // Pending
// Wraps around by default:
Status wrapped = Status.Archived.Next(); // Pending (wraps to the first member)
// Disable wrapping to get an exception at the boundary instead:
Status.Archived.Next(wrap: false); // throws InvalidOperationException
bool inRange = Status.Active.InRange(Status.Pending, Status.Inactive); // true
Custom Attributes
Fetch any custom attribute declared on an enum member — not just [Description]/[Display].
public enum Status
{
[MyCustomAttribute(Weight = 10)]
Active = 1
}
var attr = Status.Active.GetAttribute<Status, MyCustomAttribute>();
int weight = attr?.Weight ?? 0;
Listing & Metadata (EnumInfo<T>)
These are static (there's no instance to hang an extension method off), so they're called as
EnumInfo<TEnum>.Method() rather than value.Method().
IReadOnlyList<Status> all = EnumInfo<Status>.GetValues(); // [Pending, Active, Inactive, Archived]
IReadOnlyList<string> names = EnumInfo<Status>.GetNames(); // ["Pending", "Active", "Inactive", "Archived"]
IReadOnlyList<string> descs = EnumInfo<Status>.GetDescriptions(); // ["Pending Review", "Currently Active", ...]
int count = EnumInfo<Status>.Count; // 4
Status min = EnumInfo<Status>.Min; // Pending
Status max = EnumInfo<Status>.Max; // Archived
Type underlying = EnumInfo<Status>.UnderlyingType; // typeof(int)
bool isFlags = EnumInfo<Status>.IsFlagsEnum; // false
Populating dropdowns / select lists
// Keyed by TEnum - the most common case for server-side binding:
IReadOnlyDictionary<Status, string> byName = EnumInfo<Status>.GetValuesAndNames();
IReadOnlyDictionary<Status, string> byDesc = EnumInfo<Status>.GetValuesAndDescriptions();
foreach (var (value, label) in byDesc)
{
Console.WriteLine($"<option value=\"{(int)value}\">{label}</option>");
}
When you need a plain numeric or string key instead of TEnum
Useful for JSON APIs, JS interop, HTML form values, or anywhere the consumer can't work with a C# enum type directly.
public enum Priority : byte
{
[Description("Low Priority")]
Low = 1,
Medium = 2,
[Description("High Priority")]
High = 3
}
// Key is boxed as the enum's REAL underlying type - here, genuinely `byte`, not `int`:
IReadOnlyDictionary<object, string> byUnderlyingType =
EnumInfo<Priority>.GetValuesAndNamesByUnderlyingType();
// byUnderlyingType[(byte)2] == "Medium"
IReadOnlyDictionary<object, string> descByUnderlyingType =
EnumInfo<Priority>.GetValuesAndDescriptionsByUnderlyingType();
// descByUnderlyingType[(byte)1] == "Low Priority"
// Key is the numeric value formatted as a string - handy for HTML <option value="...">,
// query string parameters, or any string-keyed dictionary:
IReadOnlyDictionary<string, string> byStringKey =
EnumInfo<Priority>.GetValuesAndNamesAsString();
// byStringKey["2"] == "Medium"
IReadOnlyDictionary<string, string> descByStringKey =
EnumInfo<Priority>.GetValuesAndDescriptionsAsString();
// descByStringKey["1"] == "Low Priority"
Because C# generic methods can't change their return type based on runtime reflection, the
...ByUnderlyingType()methods returnIReadOnlyDictionary<object, string>— but each key is genuinely boxed as the enum's real underlying primitive (int,byte,sbyte,short,ushort,uint,long, orulong), not always boxed asint. A lookup with the wrong boxed numeric type (e.g. a boxedintagainst abyte-backed enum's dictionary) will correctly miss, since boxed value types only compare equal to boxed values of the exact same type.
Flags Enums
Operations specific to [Flags] enums, in FlagsEnumExtensions.
[Flags]
public enum Permissions
{
None = 0,
Read = 1,
Write = 2,
Execute = 4,
Delete = 8,
All = Read | Write | Execute | Delete
}
var value = Permissions.Read | Permissions.Write;
// Faster, non-boxing equivalent of the built-in Enum.HasFlag:
bool canRead = value.HasFlagFast(Permissions.Read); // true
// Immutable "with" style mutation - each call returns a new value:
value = value.AddFlag(Permissions.Execute); // Read | Write | Execute
value = value.RemoveFlag(Permissions.Write); // Read | Execute
value = value.ToggleFlag(Permissions.Delete); // Read | Execute | Delete
// Decompose a combined value into its individual single-bit flags:
IReadOnlyList<Permissions> setFlags = value.GetSetFlags(); // [Read, Execute, Delete]
// Validate that a value contains only bits that correspond to defined flags
// (catches stray/garbage bits that Enum.IsDefined() wouldn't reliably catch for combinations):
bool valid = value.IsValidFlagsCombination(); // true
bool invalid = ((Permissions)64).IsValidFlagsCombination(); // false - bit 64 isn't a defined flag
Exception Handling
Every "strict" conversion method (ToEnum<T>(), DescriptionToEnum<T>(), Next()/Previous()
without wrap) throws Brainy.Enums.EnumConversionException on invalid input, carrying the
target enum type and the attempted value:
try
{
var status = "NotAStatus".ToEnum<Status>();
}
catch (EnumConversionException ex)
{
Console.WriteLine($"{ex.AttemptedValue} is not valid for {ex.EnumType?.Name}");
}
Every strict method has a matching Try... non-throwing counterpart for validation-heavy or
hot-path code where exceptions aren't desirable.
Building from Source
git clone https://github.com/yourname/Brainy.Enums.git
cd Brainy.Enums
dotnet build
dotnet test
dotnet pack src/Brainy.Enums/Brainy.Enums.csproj -c Release -o ./artifacts
dotnet nuget push ./artifacts/Brainy.Enums.1.0.0.nupkg \
--api-key <YOUR_NUGET_API_KEY> \
--source https://api.nuget.org/v3/index.json
Project layout
Brainy.Enums.sln
src/Brainy.Enums/
EnumExtensions.cs # number/name/description conversions, validation, ordering, attributes
EnumInfo.cs # static listing/metadata (GetValues, GetNames, Min, Max, dropdown helpers, ...)
FlagsEnumExtensions.cs # [Flags]-specific helpers
EnumConversionException.cs
Internal/EnumCache.cs # one-time reflection cache per enum type
tests/Brainy.Enums.Tests/ # xUnit test suite
Design Notes
- Every conversion method has both a throwing form (e.g.
ToEnum<T>()) and a non-throwingTry...form, so you can pick the right style for hot paths vs. validation paths. HasFlagFastavoids the boxing allocation that the built-inEnum.HasFlagincurs on every call.ToDescription()always falls back to the member name when no[Description]attribute is present, so it's safe to call unconditionally in display code.- Reflection is performed exactly once per enum type (cached in
EnumCache<T>), so repeated calls are just dictionary/array lookups. - The library is reflection-based under the hood, which makes it very convenient but not currently trimming / Native AOT safe. A source-generator-based version is a natural next step if you need that.
Contributing
Contributions, ideas, feature requests, and bug reports are always welcome.
If you'd like to improve Brainy.Enums, feel free to open an issue or submit a pull request.
Author
Brainy.Mediator is created and maintained by Shakeel Iqbal, a Senior .NET Architect and C# Developer with extensive experience building enterprise applications and software solutions using the Microsoft technology stack.
- LinkedIn: Shakeel Iqbal
- Company: Brainy Solutions
License
This project is licensed under the MIT License.
About Brainy Solutions
We build modern software solutions using .NET, cloud technologies, AI, and enterprise architecture. As part of our commitment to the developer community, we actively open-source tools and libraries that help simplify software development.
If you find Brainy.Mediator
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.