Splitting the NetEscapades.EnumGenerators packages: the road to a stable release The article describes the restructuring of the **NetEscapades.EnumGenerators** NuGet package, a source generator that creates fast `ToStringFast()` methods for enums to improve performance over built-in .NET methods. The package has been split into three separate packages to better accommodate different user needs, with the author aiming for a stable 1.0.0 release soon. The post explains the motivation for the split, including the addition of new features like disabling number parsing and automatic case conversion, and encourages users to report issues before the final release. In this post I describe some of the significant restructuring 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 describe what motivated the restructuring, and finally what the changes are and a call to action. The tl;dr;is that there are nowthreedifferent packages, and exactly which one is best for you depends on what you're trying to do. Check the section below or the project's README for details As an aside, I really want to give this package a stable 1.0.0 release shortly, as I think we've solved most of the corner cases that were bugging me. Which means if the new package structure doesn't work for you, now is the time to raise an issue https://github.com/andrewlock/NetEscapades.EnumGenerators/issues , before it gets set in stone 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 | - | - | | ToString | net10.0 | 6.4389 ns | 0.1038 ns | 0.0971 ns | 1.000 | 0.0038 | 24 B | | ToStringFast | net10.0 | 0.0050 ns | 0.0202 ns | 0.0189 ns | 0.001 | - | - | Even though recent versions of .NET are way faster, 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. That's the basics of the package, for more details see the project's README https://github.com/andrewlock/NetEscapades.EnumGenerators/ . In the next section I describe how adding some new features managed to break users, and what we did in response. Adding new features by adding to the marker attribute dll adding-new-features-by-adding-to-the-marker-attribute-dll In version 1.0.0-beta19 of NetEscapades.EnumGenerators I introduced a bunch of new features /updates-to-netescapaades-enumgenerators-new-apis-and-system-memory-support/ that had been long standing https://github.com/andrewlock/NetEscapades.EnumGenerators/issues/80 requests https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/177 : - Support for disabling number parsing. - Support for automatically calling ToLowerInvariant or ToUpperInvariant on the serialized enum. There wasn't really a technical reason I took so long to add these features. The problem was that I didn't want to add dozens of different overloads of Enum.Parse or ToString to accommodate all the different possible options. Similarly, I didn't want to add these all as different IMO, ugly additional extension methods. I solved this issue in what I thought was a neat way. When you referenced the NetEscapades.EnumGenerators package, your library references the NetEscapades.EnumGenerators.Attributes.dll file that is shipped in the package: This is just one way to add "marker" attributes to a target application, and until recently /exploring-dotnet-10-preview-features-4-solving-the-source-generator-marker-attribute-problem-in-dotnet-10/ it was the most reliable way. My epiphany was to realise that I could put other types in this dll too; including types that are part of the target app's "public" API. So I added EnumParseOptions and SerializationOptions types to NetEscapades.EnumGenerators.Attributes.dll , and used these types to add "general, extensible" overloads for the generated Parse and ToString methods, something like this: public static partial class ColourExtensions { // EnumParseOptions controls case sensitivity, number parsing, matching metadata attributes public static Colour Parse string? name, in EnumParseOptions options ; // SerializationOptions controls case transforms and whether to use metadata attributes public static string ToString Color valu, in SerializationOptions options } This all seemed very neat. If we want to add more features, we can just extend the options objects, no need for new methods or anything. However, I inadvertently managed to break a bunch of users πŸ€¦β™‚οΈ When new features break users… when-new-features-break-users Shortly after publishing the new version of the package, I received reports https://github.com/andrewlock/NetEscapades.EnumGenerators/issues/231 of users seeing the following error: Error CS0012: The type 'EnumParseOptions' is defined in an assembly that is not referenced. You must add a reference to assembly 'NetEscapades.EnumGenerators.Attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. 184, 11 The issue seemed pretty clear: the NetEscapades.EnumGenerators.Attributes.dll that contains the new options objects used in the API wasn't being referenced in the final application. As I said in my initial response https://github.com/andrewlock/NetEscapades.EnumGenerators/issues/231 issuecomment-3716473001 : I'm guessing that you're excluding assets where you reference the package, e.g. something like this?