Recent updates to NetEscapades.EnumGenerators: [EnumMember] support, analyzers, and bug fixes The article summarizes recent updates to the NetEscapades.EnumGenerators NuGet package, a source generator that creates fast methods for working with enums in .NET. The updates in version 1.0.0-beta.16 include redesigned support for metadata attributes like `[Display]` and `[Description]`, new analyzers to ensure correct usage of the `[EnumExtensions]` attribute, and bug fixes for edge cases. The package generates optimized `ToStringFast()` methods using switch statements, which are significantly faster than the built-in `ToString()` for known enum values. In this post I describe some of the recent updates to my source generator NuGet package NetEscapades.EnumGenerators https://github.com/andrewlock/NetEscapades.EnumGenerators which you can use to add fast methods for working with enum s. I start by describing why the package exists and what you can use it for, then I walk through some of the recent changes. Why should you use an enum source generator? why-should-you-use-an-enum-source-generator- NetEscapades.EnumGenerators https://github.com/andrewlock/NetEscapades.EnumGenerators was one of the first source generators I created using the incremental generator support introduced in .NET 6 /exploring-dotnet-6-part-9-source-generator-updates-incremental-generators/ . I chose to create this package to work around an annoying characteristic of working with enums: some operations are surprisingly slow. Note that while this has historicallybeen true, this fact won't necessarily remain true forever. In fact, .NET 8+ provided a bunch of improvements to enum handling in the runtime. As an example, let's say you have the following enum: public enum Colour { Red = 0, Blue = 1, } At some point, you want to print the name of a Color variable, so you create this helper method: public void PrintColour Colour colour { Console.WriteLine "You chose "+ colour.ToString ; // You chose Red } While this looks like it should be fast, it's really not. NetEscapades.EnumGenerators works by automatically generating an implementation that is fast. It generates a ToStringFast method that looks something like this: public static class ColourExtensions { public string ToStringFast this Colour colour = colour switch { Colour.Red = nameof Colour.Red , Colour.Blue = nameof Colour.Blue , = colour.ToString , } } } This simple switch statement checks for each of the known values of Colour and uses nameof to return the textual representation of the enum . If it's an unknown value, then it falls back to the built-in ToString implementation to ensure correct handling of unknown values for example this is valid C : PrintColour Colour 123 . If we compare these two implementations using BenchmarkDotNet https://benchmarkdotnet.org/ for a known colour, you can see how much faster ToStringFast implementation is: | Method | FX | Mean | Error | StdDev | Ratio | Gen 0 | Allocated | |---|---|---|---|---|---|---|---| | ToString | net48 | 578.276 ns | 3.3109 ns | 3.0970 ns | 1.000 | 0.0458 | 96 B | | ToStringFast | net48 | 3.091 ns | 0.0567 ns | 0.0443 ns | 0.005 | - | - | | ToString | net6.0 | 17.985 ns | 0.1230 ns | 0.1151 ns | 1.000 | 0.0115 | 24 B | | ToStringFast | net6.0 | 0.121 ns | 0.0225 ns | 0.0199 ns | 0.007 | - | - | These numbers are obviously quite old now, but the overall pattern hasn't changed: .NET is way faster than .NET Framework, and the ToStringFast implementation is way faster than the built-in ToString . Obviously your mileage may vary and the results will depend on the specific enum you're using, but in general, using the source generator should give you a free performance boost. If you want to learn more about what the package provides, check my blog posts or see the project README . That covers the basics, now let's look at what's new. Updates in 1.0.0-beta.16 updates-in-1-0-0-beta-16 Version 1.0.0-beta16 of NetEscapades.EnumGenerators https://www.nuget.org/packages/NetEscapades.EnumGenerators/ was released to nuget.org on 4th November and included a number of quality of life features and bug fixes. I'll describe each of the updates in more detail below, but they fall into one of three categories: - Redesign of how "additional metadata attributes" such as Display and Description work. - Additional analyzers to ensure EnumExtensions is used correctly - Bug fixes for edge cases Let's start by looking at the updated metadata attribute support. Updated metadata attribute and updated-metadata-attribute-and-enummember-support EnumMember support EnumMember supportFor a long time, you've been able to use Display or Description attributes applied to enum members to customize how ToStringFast or Parse works with the library. For example, if you have the following enum : EnumExtensions public enum MyEnum { First, Display Name = "2nd" Second, } Then three different ToString methods are generated: Two overloads of ToStringFast and ToStringFastWithMetadata : public static partial class MyEnumExtensions { // Use a boolean to decide whether to use "metadata" attributes public static string ToStringFast this MyEnum value, bool useMetadataAttributes = useMetadataAttributes ? value.ToStringFastWithMetadata : value.ToStringFast ; // Use the raw enum member names public static string ToStringFast this MyEnum value = value switch { MyEnum.First = nameof MyEnum.First , MyEnum.Second = nameof MyEnum.Second , = value.ToString , }; // Use metadata attributes if provided, and fallback to raw enum member names private static string ToStringFastWithMetadata this MyEnum value = value switch { MyEnum.First = nameof MyEnum.First , MyEnum.Second = "2nd", // ๐Ÿ‘ˆ from the metadata names = value.ToString , }; // ... more generated members } The ability to use these additional metadata values can be very useful, and I've used them frequently. For a long time I supported Display and Description attributes, but there was a request https://github.com/andrewlock/NetEscapades.EnumGenerators/issues/73 to support EnumMember as well. The problem was when you had multiple metadata attributes on enum membersโ€”which one should the attribute use? Previously the generator arbitrarily chose Display preferentially, and fell back to Description . But there was no good reason for that ordering, it was entirely due to one being implemented before the other๐Ÿ˜ฌ And adding EnumMember as another fallback just felt too nasty.๐Ÿ˜… So instead, in 163 https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/163 , I added explicit support for EnumMember but also updated the code so that you could only use a single metadata attribute source for a given enum. That means only a single type of metadata attribute is considered for a given enum. You can select the source to use by setting the MetadataSource property on the EnumExtensions attribute. In the example below, the generated source explicitly opts in to using Display attributes: EnumExtensions MetadataSource = MetadataSource.DisplayAttribute public enum EnumWithDisplayNameInNamespace { First = 0, Display Name = "2nd" Second = 1, Third = 2, } Any other metadata attributes Description , EnumMember applied to members in the above enum would be ignored. Alternatively, you can use MetadataSource.None to choose none of the metadata attributes. In this case, the overloads that take a useMetadataAttributes parameter will not be emitted. This was a breaking change on its own, but there was an even bigger change: the defaultmetadata source has been changed to EnumMember as a better semantic choice for these attributes. You can change the default metadata source to use for a whole project by setting the EnumGenerator EnumMetadataSource property in your project: