Library / SDK
andrewlock/NetEscapades.EnumGenerators avatar
andrewlock/NetEscapades.EnumGenerators

NetEscapades.EnumGenerators: compile-time enum helpers for C#

A source generator for generating fast "reflection" methods for enums

1,125 stars57 forksC#MIT

At a glance

What is it?
NetEscapades.EnumGenerators is a Roslyn source generator that turns an attributed enum into a partial class of extension methods such as ToStringFast(), so you stop paying for reflection at runtime. It suits .NET developers on hot paths where enum ToString() and HasFlag() show up in a profile, and it costs you an SDK floor and a source generator reference.
Who is it for?
Adopt NetEscapades.EnumGenerators when enum ToString() or HasFlag() appears in a profile of your .NET service and you want the fast path without writing the switch yourself, and you can hold the .NET 7 SDK floor. Skip it if the enums are never on a hot path, because a hand-written switch expression is cheaper than a package reference, and treat the interceptor package as a separate decision since it is marked experimental.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 166 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Enum ToString() is slow for reasons the runtime cannot avoid

The project starts from an uncomfortable fact about the .NET base class library. Calling ToString() or HasFlag() on an enum looks like it should be cheap, and the README states plainly that many methods working with enums are surprisingly slow, with ToString() and HasFlag() named as the examples. The reason is that both go through reflection or the general formatting path to produce a name, and the general path allocates. NetEscapades.EnumGenerators takes that away at compile time. The package is a source generator that writes extension methods for enums, described as fast reflection, and the README shows a benchmark table where ToStringFast() on net48 sits at 3.091 ns against 578.276 ns for ToString(), and on net6.0 at 0.121 ns against 17.985 ns. The generated methods report zero allocation in the table, where the framework versions allocate 96 B and 24 B.

One attribute on the enum is the entire opt-in

The mechanism is a Roslyn source generator, which means the code does not exist until the compiler runs. Adding the package reference automatically adds a marker attribute, and marking an enum with that attribute is what triggers generation. The README shows the shape of the opt-in:

csharp
[EnumExtensions]
public enum Color
{
    Red = 0,
    Blue = 1,
}

The generator then produces a partial class named after the enum, `MyEnumExtensions` by default, containing a `Length` constant and the helper methods. The generated `ToStringFast()` is a switch expression over the enum members that returns `nameof(...)` for each known value and falls back to `value.ToString()` for anything unrecognised, which is what keeps it correct when a value arrives that was never declared. A second overload takes a `bool useMetadataAttributes` flag, and when it is set the generator uses metadata-aware string values instead.

EnumMember attributes change what the fast string returns

The metadata path is the part worth understanding before you rely on the output. The README demonstrates an enum where a member carries an `EnumMember` attribute with an explicit value:

csharp
[EnumExtensions]
public enum MyEnum
{
    First,

    [EnumMember(Value = "2nd")]
    Second,
}

In the generated class, the plain `ToStringFast()` returns `nameof(MyEnum.First)` and `nameof(MyEnum.Second)`, while the private `ToStringFastWithMetadata()` returns `nameof(MyEnum.First)` and the literal `"2nd"`. The public `ToStringFast(this MyEnum value, bool useMetadataAttributes)` chooses between the two at the call site. This is a meaningful difference for serialisation and API contracts, because a wire format that says `2nd` and a log line that says `Second` are not interchangeable, and picking the wrong overload is a silent behaviour change rather than a compile error.

Adding the package and reading the generated output

The install step is one command from the .NET CLI:

bash
dotnet add package NetEscapades.EnumGenerators

That writes a PackageReference into the project file, which looks like this:

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>

  <!-- Add the package -->
  <PackageReference Include="NetEscapades.EnumGenerators" Version="1.0.0-beta21" />
  <!-- -->

</Project>

Note the version string in the README example, which is a beta, and note the warning attached to the reference: the documentation says you should not use `PrivateAssets` when referencing NetEscapades.EnumGenerators because the package has runtime dependencies. The `samples/ToStringFastExample/` directory in the repository shows a working sample project, and the generated code can be inspected in the IDE output of the compiler, which is the quickest way to confirm the extension class name and the members you actually got.

A metapackage with a dependency-free option underneath

NetEscapades.EnumGenerators is described as a metapackage, with a section in the README devoted to package referencing options. The point of that section is to let you choose which underlying packages you take and whether you accept runtime dependencies. The README offers a way to avoid runtime dependencies, and a decision table for choosing the correct packages for your scenario, which is where the detail lives. That structure matters for one practical reason: a source generator that also drags runtime assemblies into your output changes what your deployed application contains, and for some teams a generator that only runs at build time is a cleaner dependency story than one that also ships a runtime library.

The interceptor package is a separate, experimental bet

A second package, NetEscapades.EnumGenerators.Interceptors, intercepts calls to `ToString()` on enums and automatically replaces them with calls to the generated `ToStringFast()`. The README is explicit about the maturity: the interceptor package is experimental and requires the .NET 8.0.400 SDK or higher, while the main package requires the .NET 7 SDK or higher. There is a dedicated section on enabling automatic interception, marked experimental in the table of contents. The SDK requirements apply to the SDK installed on the machine, not to the framework you target, so you can still target .NET Core 3.1 and earlier while the build runs on a newer SDK. Reading the interceptor as a code transform rather than a runtime library is the right mental model, and it is the part of this project where the risk is highest.

The rough edges: analyzers, name collisions and package shape

Several sections of the README point at friction rather than features. There is a section on enabling the analyzers and another on configuring analyzer severity, with code fixes offered for the issues they find, and a section on resolving overload ambiguity with `new()` when a generated method collides with an existing one. A dedicated section covers preserving usages of the `[EnumExtensions]` attribute, which implies the generator can rewrite or remove those attribute applications in generated output, and code that depends on the attribute being present in the final assembly needs to know that. The build side of the repository is heavier than a typical single-package repo: there is a `.nuke/` directory with `build.cmd`, `build.ps1` and `build.sh`, a `global.json`, `Directory.Build.props`, `version.props` and `releasenotes.props`, plus separate `src/`, `tests/` and `samples/` trees and a NuGet integration test configuration.

The alternative: hand-written switches and cached name arrays

The comparison is not with another package so much as with the two things you can do without any generator. The first is a hand-written switch expression, which is exactly what the generator emits, so a code generator saves you the typing and keeps it in sync when members are added, but it does not produce faster code than the switch you would have written. The second is a static lookup table built once from `Enum.GetValues` and `Enum.GetName`, cached in a dictionary, which avoids the per-call formatting cost without a source generator, at the price of a runtime initialisation and a lookup on every call rather than a jump table. The generator wins on allocation and on having the mapping known at compile time, which matters when the enum is used in a hot loop. It loses on build complexity and on the fact that the generated code is not visible in your own source, so the compile step becomes part of how you debug the behaviour.

Editorial conclusion

Adopt NetEscapades.EnumGenerators when enum ToString() or HasFlag() appears in a profile of your .NET service and you want the fast path without writing the switch yourself, and you can hold the .NET 7 SDK floor. Skip it if the enums are never on a hot path, because a hand-written switch expression is cheaper than a package reference, and treat the interceptor package as a separate decision since it is marked experimental. Before merging, check the generated extension class name, since it defaults to the enum name plus Extensions, and confirm your enum has no duplicate numeric values, since a switch cannot map the same value to two names.

Frequently asked questions

How do I add NetEscapades.EnumGenerators to a C# project?

Run `dotnet add package NetEscapades.EnumGenerators`, which adds a PackageReference to the project file, then mark the enums you want fast helpers for with the `[EnumExtensions]` attribute.

Which .NET SDK does NetEscapades.EnumGenerators require?

The main package requires the .NET 7 SDK or higher. The Interceptors package is experimental and requires the .NET 8.0.400 SDK or higher. The requirement applies to the installed SDK, so you can still target earlier frameworks.

What does ToStringFast() generate and how does it handle unknown values?

It generates a switch expression over the enum members returning nameof for each known value, and falls back to value.ToString() for values that are not declared in the enum.

What does NetEscapades.EnumGenerators.Interceptors do?

It intercepts calls to ToString() on enums and automatically replaces them with calls to the ToStringFast() method generated by NetEscapades.EnumGenerators. The README marks it as experimental.

Should I set PrivateAssets when referencing NetEscapades.EnumGenerators?

The README warns against it, because the package has runtime dependencies. The package referencing options section describes alternative approaches for avoiding those runtime dependencies.

Official sources

  1. andrewlock/NetEscapades.EnumGenerators on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/andrewlock-netescapades-enumgenerators.svg)](https://hysenlabs.com/projects/andrewlock-netescapades-enumgenerators)

Was this useful?