Ever tried to feed an enum into a dropdown, send it as JSON, or store it in a cache and hit a wall? You’re not alone. Enums look static, but in day‑to‑day .NET code they often need to act like arrays you can loop, filter, sort, and serialize.
In this post I’ll show clean, safe ways to turn any C# enum into the arrays you need: values, names, pairs for UI, numeric forms for storage, and more. We’ll cover modern APIs, [Flags] enums, caching for speed, JSON tricks, and a few sharp edges I learned the hard way on real projects.
When and why you need enum → array
You’ll likely need an array when you:
- Build UI: dropdowns, checkboxes, radio groups, or a table filter.
- Expose APIs: send enum names or codes to the client as arrays.
- Persist data: store numeric codes in a DB or cache.
- Validate input: check if an incoming value is part of the enum.
Real story from my side: I once wired a settings page that had three dozen dropdowns. Rendering each list with Enum.GetValues per request hurt allocations. Caching a ready‑to‑use array per enum type fixed it and shaved a few milliseconds from every page load.
Quick start: 7 common conversions
Let’s use this simple enum for examples:
public enum OrderStatus : byte
{
New = 1,
Paid = 2,
Shipped = 3,
Cancelled = 4
}1) Enum values as a strongly typed array
OrderStatus[] values = Enum.GetValues<OrderStatus>();
// values: [New, Paid, Shipped, Cancelled]Works on .NET 5+ and is fast and type‑safe.
2) Enum names as an array of strings
string[] names = Enum.GetNames<OrderStatus>();
// names: ["New", "Paid", "Shipped", "Cancelled"]3) Pairs for UI: Name + Value
var items = Enum
.GetValues<OrderStatus>()
.Select(v => new { Name = v.ToString(), Value = v })
.ToArray();4) Add numeric code (for storage or client apps)
var itemsWithCode = Enum
.GetValues<OrderStatus>()
.Select(v => new { Name = v.ToString(), Value = v, Code = Convert.ToInt32(v) })
.ToArray();5) Numeric array only
int[] codes = Enum.GetValues<OrderStatus>()
.Select(v => Convert.ToInt32(v))
.ToArray();If you know the underlying type, you can cast directly (e.g.,
(byte)v). For generic code, preferConvert.ToInt64/ToInt32.
6) Sorted by display name (nice for UI)
using System.ComponentModel.DataAnnotations;
public enum ShippingMethod
{
[Display(Name = "Air Freight")] Air = 1,
[Display(Name = "Sea Freight")] Sea = 2,
[Display(Name = "Road")] Road = 3
}
static string GetDisplayName<T>(T value) where T : struct, Enum
{
var mi = typeof(T).GetMember(value.ToString()).FirstOrDefault();
var disp = mi?.GetCustomAttributes(typeof(DisplayAttribute), false)
.OfType<DisplayAttribute>()
.FirstOrDefault()?.GetName();
return disp ?? value.ToString();
}
var ui = Enum.GetValues<ShippingMethod>()
.Select(v => new { Id = Convert.ToInt32(v), Name = GetDisplayName(v) })
.OrderBy(x => x.Name)
.ToArray();7) Legacy approach (.NET Framework / older targets)
// When Enum.GetValues<T>() isn’t available
var legacyValues = Enum.GetValues(typeof(OrderStatus))
.Cast<OrderStatus>()
.ToArray();A reusable helper you can drop into any project
I like to keep one tiny helper that returns arrays, already cached per enum type. This cuts allocations in hot paths (web views, API endpoints, and so on).
public static class EnumArray<T> where T : struct, Enum
{
// One-time cached arrays per T
public static readonly T[] Values = Enum.GetValues<T>();
public static readonly string[] Names = Enum.GetNames<T>();
public static readonly (string Name, T Value, long Code)[] Items = Values
.Select(v => (v.ToString(), v, Convert.ToInt64(v)))
.ToArray();
}Usage:
var values = EnumArray<OrderStatus>.Values; // OrderStatus[]
var names = EnumArray<OrderStatus>.Names; // string[]
var items = EnumArray<OrderStatus>.Items; // (Name, Value, Code)[]Note:
Enum.GetValues<T>()creates a new array each call. Caching avoids repeated allocations if you access the list many times.
[Flags] enums: include only atomic bits
With flags you often want just single‑bit items for checkboxes. Example:
[Flags]
public enum Permissions
{
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Execute = 1 << 2,
All = Read | Write | Execute
}If you call Enum.GetValues<Permissions>(), you’ll get None, Read, Write, Execute, and All. Most UIs shouldn’t show All. Filter to atomic flags:
static bool IsPowerOfTwo(long x) => x != 0 && (x & (x - 1)) == 0;
Permissions[] atomic = Enum.GetValues<Permissions>()
.Where(p => p == Permissions.None || IsPowerOfTwo(Convert.ToInt64(p)))
.ToArray();Tip: if you also add composite “roles” like
Editor = Read | Write, the same filter hides them for UI. You can expose them separately if needed.
Serialize arrays to JSON (System.Text.Json)
Two common needs:
- Send enum names (human‑readable) to clients.
- Send numeric codes (compact, stable) to clients or store them.
Names as strings
using System.Text.Json;
using System.Text.Json.Serialization;
var options = new JsonSerializerOptions
{
WriteIndented = true
};
options.Converters.Add(new JsonStringEnumConverter());
string jsonNames = JsonSerializer.Serialize(
EnumArray<OrderStatus>.Values, // will be written as ["New", "Paid", ...]
options);Numeric codes
int[] codes = EnumArray<OrderStatus>.Values
.Select(v => Convert.ToInt32(v))
.ToArray();
string jsonCodes = JsonSerializer.Serialize(codes, new JsonSerializerOptions { WriteIndented = true });DTO for UI
var dto = EnumArray<OrderStatus>.Values
.Select(v => new { id = Convert.ToInt32(v), name = v.ToString() })
.ToArray();
string jsonDto = JsonSerializer.Serialize(dto, new JsonSerializerOptions { WriteIndented = true });Flags note:
JsonStringEnumConverterwrites combined flags as comma‑separated names (e.g.,"Read, Write"). If your client can’t parse that, send numeric codes instead.
ASP.NET Core: serve enum arrays to the client
Minimal API endpoint that returns a ready list for your frontend:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/api/order-status", () => EnumArray<OrderStatus>.Items);
app.Run();This returns an array of { Name, Value, Code } objects. If you prefer plain strings for names:
app.MapGet("/api/order-status/names", () => EnumArray<OrderStatus>.Names);Or a strict DTO:
public record EnumOption(int Id, string Name);
app.MapGet("/api/order-status/options",
() => EnumArray<OrderStatus>.Values
.Select(v => new EnumOption(Convert.ToInt32(v), v.ToString()))
.ToArray());UI example: Razor Pages / MVC dropdown
@model MyPageModel
<select asp-for="Status" asp-items="Model.StatusItems"></select>using Microsoft.AspNetCore.Mvc.Rendering;
public class MyPageModel : PageModel
{
[BindProperty]
public OrderStatus Status { get; set; }
public IEnumerable<SelectListItem> StatusItems { get; private set; } = default!;
public void OnGet()
{
StatusItems = EnumArray<OrderStatus>.Values
.Select(v => new SelectListItem
{
Text = v.ToString(),
Value = Convert.ToInt32(v).ToString()
})
.ToArray();
}
}For checkboxes with flags, use the atomic filter we built earlier to avoid composite values.
Safety checks and parsing back from strings or codes
- Validate before saving:
Enum.IsDefined(typeof(OrderStatus), value)checks if an int maps to a defined member. - Parse safely from string:
Enum.TryParse<OrderStatus>(text, ignoreCase: true, out var v). - Reject composites for non‑flags: if you accept a number, also verify
Enum.IsDefinedto avoid unknown codes.
Example:
public static bool TryParseCode<T>(int code, out T value) where T : struct, Enum
{
value = (T)Enum.ToObject(typeof(T), code);
return Enum.IsDefined(typeof(T), value);
}Performance notes
Enum.GetValues<T>()is very fast but allocates a new array each call. Cache the result (as shown) if you use it a lot.- Turning values into DTOs for UI should also be cached if possible (e.g., static field, memory cache). It’s tiny data and safe to reuse.
- For flags, filtering atomic items does a small LINQ pass. If it’s in a hot path, precompute and store the array.
Tricky bits and gotchas
1) Order of valuesEnum.GetValues<T>() returns members in declaration order, not numeric order. If you need numeric order, sort by Convert.ToInt64(v).
2) Duplicate numeric values
It’s legal (though confusing) for multiple names to share the same number. ToString() picks the first matching name by spec. If you rely on unique codes, enforce it yourself.
3) [Flags] and “All” membersAll = Read | Write | Execute is handy for code, but almost never belongs in a UI list. Filter it out.
4) Underlying type
Enums can be byte, short, int, long etc. When you write generic helpers, use Convert.ToInt64 for math and comparisons, then cast as needed at the edge.
5) Display namesDisplayAttribute is great for UI text. If you have more than one culture, consider resource‑based names ([Display(Name = nameof(Resources.SomeKey), ResourceType = typeof(Resources))]).
Complete utility: values, names, display names, flags filter, and JSON
Drop this into a shared project and you’re set.
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using System.Text.Json.Serialization;
public static class EnumToolkit<T> where T : struct, Enum
{
public static readonly T[] Values = Enum.GetValues<T>();
public static readonly string[] Names = Enum.GetNames<T>();
public static readonly (string Name, T Value, long Code)[] Items = Values
.Select(v => (v.ToString(), v, Convert.ToInt64(v)))
.ToArray();
public static string GetDisplayName(T value)
{
var mi = typeof(T).GetMember(value.ToString()).FirstOrDefault();
var attr = mi?.GetCustomAttributes(typeof(DisplayAttribute), false)
.OfType<DisplayAttribute>()
.FirstOrDefault();
return attr?.GetName() ?? value.ToString();
}
public static (string Name, T Value, long Code)[] ItemsWithDisplay()
=> Values.Select(v => (GetDisplayName(v), v, Convert.ToInt64(v)))
.OrderBy(x => x.Item1)
.ToArray();
public static T[] AtomicFlagsOrAll()
{
static bool IsPowerOfTwo(long x) => x != 0 && (x & (x - 1)) == 0;
return Values
.Where(v => IsPowerOfTwo(Convert.ToInt64(v)) || Convert.ToInt64(v) == 0)
.ToArray();
}
public static string ToJsonNames()
{
var options = new JsonSerializerOptions { WriteIndented = true };
options.Converters.Add(new JsonStringEnumConverter());
return JsonSerializer.Serialize(Values, options);
}
public static string ToJsonCodes()
=> JsonSerializer.Serialize(Values.Select(v => Convert.ToInt64(v)).ToArray(),
new JsonSerializerOptions { WriteIndented = true });
}Usage samples:
var namesJson = EnumToolkit<OrderStatus>.ToJsonNames();
var codesJson = EnumToolkit<OrderStatus>.ToJsonCodes();
var uiItems = EnumToolkit<OrderStatus>.ItemsWithDisplay();Testing the arrays (NUnit/xUnit)
Small tests go a long way, especially when enums change over time.
[Fact]
public void OrderStatus_values_are_cached_and_sorted_by_declaration()
{
var values = EnumArray<OrderStatus>.Values;
values.Should().Equal(new[]
{
OrderStatus.New,
OrderStatus.Paid,
OrderStatus.Shipped,
OrderStatus.Cancelled
});
}
[Fact]
public void Permissions_atomic_flags_exclude_composites()
{
var atomic = EnumToolkit<Permissions>.AtomicFlagsOrAll();
atomic.Should().BeEquivalentTo(new[]
{
Permissions.None,
Permissions.Read,
Permissions.Write,
Permissions.Execute
});
}FAQ: enum → array in real projects
Yes, but names can change during refactors. Numbers are stable if you never reorder or renumber. Pick one rule and stick to it.
EnumMember attributes?For System.Text.Json you don’t. For Newtonsoft.Json or some external contracts you might use EnumMember(Value = "...") to fix the wire format.
For UI, add a “Select…” item with empty value. For parsing, handle null/empty and skip Enum.TryParse.
Source generators can build static arrays for zero runtime work. It’s a nice extra if this is on a critical path.
Conclusion: enums to arrays without pain
You saw practical ways to turn enums into arrays you can loop, display, and serialize. We covered modern APIs, flags handling, display names, JSON formats, and caching. Take the helper shown here, drop it into your solution, and wire your UI or API in minutes.
What enum arrays do you need most often-names for UI, codes for storage, or both? Share your case in the comments and I’ll add a tailored snippet.
