PMart.Enumeration.Generator
April 16, 2025 ยท View on GitHub
This is the package to generate automatically Enumeration classes and its members (more information in the main page).
Creating a new Enumeration class is a little bit verbose. For instance, you can't forget to extend Enumeration<T> and to create the private constructor (else, it wouldn't compile anyway).
Therefore, the package PMart.Enumeration.Generator was added to help on that. It is an incremental generator.
Installation
Add the package to your project:
dotnet add package PMart.Enumeration.Generator
You need to keep the package
PMart.Enumerationinstalled.
To avoid any project referring your project getting a reference to the PMart.Enumeration.Generator, you can add PrivateAssets="all" to the package reference.
And you can also add ExcludeAssets="runtime", to avoid the PMart.Enumeration.Generator.dll file being copied to your build output (it is not required at runtime, it is a generator, so it works in compile time only):
<Project Sdk="Microsoft.NET.Sdk">
<!-- ... -->
<PackageReference Include="PMart.Enumeration" Version="3.1.0" />
<PackageReference Include="PMart.Enumeration.Generator" Version="3.1.0" PrivateAssets="all" ExcludeAssets="runtime" />
<!-- ... -->
</Project>
Usage
To create a new Enumeration with the generator, it is easy:
- Create a
partialclass, for the Enumeration class. - Add the
EnumerationAttribute(namespacePMart.Enumeration.Generator.Attributes) on the partial class. - Add fields of type
private static readonly stringnamed with the prefixValueFor(this prefix is one of the ways of doing it, as you can check next). These fields hold the values that will be used to create the enumeration members (check the examples bellow).
Any non-field or field that is not
private static readonly stringwill be ignored.
For example, without the generator, the communication type enumeration was like this:
using PMart.Enumeration;
namespace Enumeration.Sample.Enumerations;
public class CommunicationType : Enumeration<CommunicationType>
{
public static readonly CommunicationType Email = new("Email");
public static readonly CommunicationType Sms = new("SMS");
public static readonly CommunicationType PushNotification = new("PushNotification");
private CommunicationType(string value) : base(value)
{
}
}
Using the generator and the prefix ValueFor (this prefix is one of the ways of doing it, as you can check next), it is just like this:
using PMart.Enumeration.Generator.Attributes;
namespace Enumeration.Generator.Sample.Enumerations;
[Enumeration]
public partial class CommunicationType
{
private static readonly string ValueForEmail = "Email";
private static readonly string ValueForSms = "SMS";
private static readonly string ValueForPushNotification = "PushNotification";
}
And the generated code will be something like this:
// <auto-generated />
namespace Enumeration.Generator.Sample.Enumerations
{
public partial class CommunicationType : Enumeration<CommunicationType>
{
public static readonly CommunicationType Email = new CommunicationType(ValueForEmail!);
public static readonly CommunicationType Sms = new CommunicationType(ValueForSms!);
public static readonly CommunicationType PushNotification = new CommunicationType(ValueForPushNotification!);
private CommunicationType(string value) : base(value)
{
}
}
}
If you don't worry about instantiating the enumeration members and your only concern is about the inheritance from Enumeration<T> and constructors, you can use the generator to build just that parts:
[Enumeration]
public partial class CommunicationType
{
public static readonly CommunicationType Email = new("Email");
public static readonly CommunicationType Sms = new("SMS");
public static readonly CommunicationType PushNotification = new("PushNotification");
}
You can check other examples in the samples.
The EnumerationMember Attribute
If you don't like the use of the prefix ValueFor to define the member names, you can use the EnumerationMemberAttribute to define the name of the enumeration member
(but remember that is not possible to two fields have the same name, it will return a compilation error if you try to do that):
[Enumeration]
public partial class CommunicationType
{
[EnumerationMember("Email")] // It will generate a member named Email, with value "Email"
private static readonly string EmailCode = "Email";
[EnumerationMember("Sms")] // It will generate a member named Sms, with value "SMS"
private static readonly string SmsCode = "SMS";
[EnumerationMember("PushNotification")] // It will generate a member named PushNotification, with value "PushNotification"
private static readonly string PushNotificationCode = "PushNotification";
}
The EnumerationIgnore Attribute
If, for some reason, you already have a field private static readonly string named ValueFor..., but you don't want it to be used to generate a new enumeration member, use the EnumerationIgnoreAttribute:
[Enumeration]
public partial class CommunicationType
{
private static readonly string ValueForEmail = "Email";
private static readonly string ValueForSms = "SMS";
private static readonly string ValueForPushNotification = "PushNotification";
[EnumerationIgnore]
private static readonly string ValueForSomeFieldThatShouldBeIgnored = "SomeValue";
}
Generate EnumerationDynamic
To generate an Enumeration class of type EnumerationDynamic<T>, enable the option IsDynamic of the EnumerationAttribute:
[Enumeration(IsDynamic = true)]
public partial class CommunicationTypeDynamic
{
private static readonly string ValueForEmail = "Email";
private static readonly string ValueForSms = "SMS";
private static readonly string ValueForPushNotification = "PushNotification";
}
The generated code will be something like this:
// <auto-generated />
namespace Enumeration.Generator.Sample.Enumerations
{
public partial class CommunicationTypeDynamic : EnumerationDynamic<CommunicationTypeDynamic>
{
public static readonly CommunicationTypeDynamic Email = new CommunicationTypeDynamic(ValueForEmail!);
public static readonly CommunicationTypeDynamic Sms = new CommunicationTypeDynamic(ValueForSms!);
public static readonly CommunicationTypeDynamic PushNotification = new CommunicationTypeDynamic(ValueForPushNotification!);
public CommunicationTypeDynamic()
{
}
private CommunicationTypeDynamic(string value) : base(value)
{
}
}
}
Generator Diagnostics
The generator tries to report errors when the user does common mistakes, namely about naming the enumeration members with names already in use. In some cases, there are no compilation errors on the user code. Without the diagnostics from the generator, the user would not know why the generator doesn't work.
For instance, assigning the same name for the enumeration member and for the field, the Enumeration class will not be generated and an error is reported:
[Enumeration]
public partial class CommunicationType
{
[EnumerationMember("Email")]
private static readonly string Email = "Email";
^^^^^ // Error ENUM0002: The name 'Email' of the Enumeration member is the same as the field name
}
Or, defining an invalid name for the enumeration member:
[Enumeration]
public partial class CommunicationType
{
// 123 is not a valid name for a class member in C#
[EnumerationMember("123")]
private static readonly string Email = "Email";
^^^^^ // Error ENUM0001: Invalid name for the Enumeration member in the EnumerationMemberAttribute
}
There are other diagnostics reported for different cases. All are of type Error with an ID like ENUMXXXX and with a descriptive message.
Generator Limitations
- The .NET versions restrictions are:
- .NET SDK: >= 8.0.100
- MSBuild/Visual Studio: >= 17.8.
- It does not work for
abstractclasses. In the example provided in Enumeration with behavior, we use anabstractclass and subclasses. When using the generator, you can do the same without beingabstract, check this sample. - It does not support nested classes (the usage of the
EnumerationAttributein a nested class does not have effect). But it supports nested namespaces.