Skip to main content

Porticle.Enumly by Carsten Jendro

NuGet / site data

Nuget GitHub last commit GitHub Repo stars

Details

Info

info

Name: Porticle.Enumly

Roslyn source generator for compile-time enum-to-enum mapping by member name with prefix stripping, nullable enum support, explicit value overrides and exhaustive coverage diagnostics.

Author: Carsten Jendro

NuGet: https://www.nuget.org/packages/Porticle.Enumly/

You can find more details at https://github.com/Machibuse/Porticle.Enumly

Source: https://github.com/Machibuse/Porticle.Enumly

Author

note

Carsten Jendro Alt text

Original Readme

note

Porticle.Enumly

A Roslyn source generator for compile-time enum-to-enum mapping. Mark a partial class with [EnumlyClass] and partial methods with [EnumlyMap] and Porticle.Enumly fills in the implementation — by member name, with prefix stripping, nullable enum support, explicit value overrides and exhaustive coverage diagnostics.

Build State

Build and Release

NuGet

NuGet Latest Version NuGet Downloads

Installation
dotnet add package Porticle.Enumly
Quick start
using Porticle.Enumly;

[EnumlyClass]
public static partial class Mapper
{
[EnumlyMap]
public static partial Foo ToFoo(Bar value);

[EnumlyMap]
public static partial Bar ToBar(Foo value);
}

public enum Foo \{ Red, Green, Blue }
public enum Bar \{ Green, Red, Blue }

The generator produces a switch-based implementation that maps members by name. If a source value has no matching target, you get a compile-time error (EM0001) — no silent fallthroughs.

Features

######### By-name matching with prefix stripping

If every member of an enum shares a common prefix that ends at a Pascal-case boundary, that prefix is ignored when matching against the other enum:

public enum Foo \{ GoldRed, GoldRose, GoldRoyal \}   // prefix: "Gold"
public enum Bar \{ BarRed, BarRose, BarRoyal \} // prefix: "Bar"

// Bar.BarRed -> Foo.GoldRed
// Bar.BarRose -> Foo.GoldRose
// Bar.BarRoyal -> Foo.GoldRoyal

The detection looks for the longest common prefix of all member names and trims it back to the last position where every member's next character is uppercase. Source and target are analyzed independently, so prefix stripping works even when only one side has a prefix.

######### Nullable enum mappings

All four direction combinations work:

[EnumlyMap]
public static partial Foo ToFoo(Bar value); // T -> U

[EnumlyMap(NullSourceValue = Bar.None)]
public static partial Foo? ToFoo(Bar value); // T -> U? (Bar.None -> null)

[EnumlyMap(NullTargetValue = Foo.Unknown)]
public static partial Foo ToFoo(Bar? value); // T? -> U (null -> Foo.Unknown)

[EnumlyMap]
public static partial Foo? ToFoo(Bar? value); // T? -> U? (null -> null)
  • NullSourceValue is a value of the source enum that should map to null on the target side. Only meaningful when the target is nullable.
  • NullTargetValue is a value of the target enum that is produced when the source is null.
  • IgnoreNullSource = true — when the source is null, throw ArgumentNullException at runtime instead of producing a target. Use this when you'd rather fail loudly than pick a target value for null.
  • For T? -> U?, null -> null happens automatically. NullSourceValue, NullTargetValue and IgnoreNullSource can still be used to override that.

When the source is nullable and the target is non-nullable, exactly one of NullTargetValue or IgnoreNullSource must be set (otherwise EM0010, error) — null input must have a defined behavior. Setting both is a contradiction and fails with EM0011.

[EnumlyMap(IgnoreNullSource = true)]
public static partial Foo ToFoo(Bar? value); // T? -> U (null -> throw)

######### Explicit value overrides

For special cases where by-name matching can't reach (because the names don't line up even after prefix stripping), use [EnumlyMapValue]:

[EnumlyMap]
[EnumlyMapValue(Bar.BarRoyalBlue, Foo.GoldRoyal)]
public static partial Foo ToFoo(Bar value);

Explicit mappings override by-name matching for the given source value. The attribute can be applied multiple times per method. A source value covered by an [EnumlyMapValue] is exempt from the coverage diagnostic.

######### Ignoring unmapped values

By default the generator enforces coverage in both directions:

  • every source value must have a target (otherwise EM0001, error)
  • every target value must be reachable from some source (otherwise EM0008, warning)

When a value is intentionally outside the mapping, opt out per-value:

[EnumlyMap]
[EnumlyMapValue(Bar.BarRoyalBlue, Foo.GoldRoyal)]
[EnumlyIgnoreSource(Bar.BarLegacy)] // suppresses EM0001 for Bar.BarLegacy
[EnumlyIgnoreTarget(Foo.GoldReserved)] // suppresses EM0008 for Foo.GoldReserved
public static partial Foo ToFoo(Bar value);
  • [EnumlyIgnoreSource(value)] — marks a source value as intentionally unmapped. At runtime, calling the method with that value throws ArgumentOutOfRangeException with a message identifying the value as excluded by [EnumlyIgnoreSource] — distinguishable from a generic unsupported value.
  • [EnumlyIgnoreTarget(value)] — marks a target value as intentionally unreachable. Purely a compile-time hint; no runtime effect.

Both attributes can be applied multiple times. The argument's enum type is verified at compile time (otherwise EM0009, error).

Diagnostics
IDSeverityMeaning
EM0001ErrorSource enum value has no matching target member (after prefix-strip and explicit overrides). Suppressible per-value with [EnumlyIgnoreSource].
EM0002ErrorMethod is not partial / has invalid signature (must take one enum and return an enum).
EM0003ErrorNullSourceValue is set but the return type is not nullable.
EM0004ErrorA specified null value is not a member of its enum.
EM0005ErrorA specified null value has the wrong enum type.
EM0006ErrorAn EnumlyMapValue argument has the wrong enum type.
EM0007ErrorThe same source value is mapped explicitly more than once.
EM0008WarningTarget enum value is not reachable from any source. Suppressible per-value with [EnumlyIgnoreTarget], or globally via .editorconfig (dotnet_diagnostic.EM0008.severity = none).
EM0009ErrorAn EnumlyIgnoreSource / EnumlyIgnoreTarget argument has the wrong enum type.
EM0010ErrorSource is nullable and target is non-nullable, but neither NullTargetValue nor IgnoreNullSource = true is set — null input has no defined behavior.
EM0011ErrorNullTargetValue and IgnoreNullSource are both set — they describe conflicting behavior for null input.

NullTargetValue and IgnoreNullSource on a non-nullable source are allowed and ignored — no diagnostic.

Example: full mapper class
using Porticle.Enumly;

[EnumlyClass]
public static partial class Mapper
{
[EnumlyMap]
[EnumlyMapValue(Bar.BarRoyalBlue, Foo.GoldRoyal)]
public static partial Foo ToFoo(Bar value);

[EnumlyMap(NullSourceValue = Noo.GooNone)]
[EnumlyMapValue(Noo.GooRoyal, Bar.BarRoyalBlue)]
public static partial Bar? ToNullableBar(Noo value);

[EnumlyMap(NullTargetValue = Noo.GooNone)]
[EnumlyMapValue(Bar.BarRoyalBlue, Noo.GooRoyal)]
public static partial Noo ToNoo(Bar? value);
}

The generator produces:

public static partial Foo ToFoo(Bar value)
{
return value switch
{
Bar.BarRed => Foo.GoldRed,
Bar.BarRose => Foo.GoldRose,
Bar.BarRoyalBlue => Foo.GoldRoyal, // explicit override
_ => throw new ArgumentOutOfRangeException(...)
};
}
License

MIT — see LICENSE.

About

note

generate enum-to-enum mappings, so one enum values can be converted to another enum type with matching names or values

How to use

Example (source csproj, source files)

This is the CSharp Project that references Porticle.Enumly

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

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Porticle.Enumly" Version="1.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>





</Project>

Generated Files

Those are taken from $(BaseIntermediateOutputPath)\GX

// <auto-generated/>
#nullable enable

namespace EnumDemo;

public static partial class Mapper
{
public static partial global::EnumDemo.TypesCar ToTypesCar(global::EnumDemo.CarTypes value)
{
return value switch
{
global::EnumDemo.CarTypes.None => global::EnumDemo.TypesCar.CarNone,
global::EnumDemo.CarTypes.Dacia => global::EnumDemo.TypesCar.CarDacia,
global::EnumDemo.CarTypes.Tesla => global::EnumDemo.TypesCar.CarTesla,
global::EnumDemo.CarTypes.BMW => global::EnumDemo.TypesCar.CarBMW,
global::EnumDemo.CarTypes.Mercedes => global::EnumDemo.TypesCar.CarMercedes,
_ => throw new global::System.ArgumentOutOfRangeException(nameof(value), value, $"The value of enum \"EnumDemo.CarTypes\" is not supported."),
};
}

public static partial global::EnumDemo.CarTypes ToCarTypes(global::EnumDemo.TypesCar value)
{
return value switch
{
global::EnumDemo.TypesCar.CarNone => global::EnumDemo.CarTypes.None,
global::EnumDemo.TypesCar.CarDacia => global::EnumDemo.CarTypes.Dacia,
global::EnumDemo.TypesCar.CarTesla => global::EnumDemo.CarTypes.Tesla,
global::EnumDemo.TypesCar.CarBMW => global::EnumDemo.CarTypes.BMW,
global::EnumDemo.TypesCar.CarMercedes => global::EnumDemo.CarTypes.Mercedes,
_ => throw new global::System.ArgumentOutOfRangeException(nameof(value), value, $"The value of enum \"EnumDemo.TypesCar\" is not supported."),
};
}
}

Useful

Download Example (.NET C#)

Share Porticle.Enumly

https://ignatandrei.github.io/RSCG_Examples/v2/docs/Porticle.Enumly

Category "Enum" has the following generators:

1 Aigamo.MatchGenerator Nuget GitHub Repo stars 2026-04-08

2 CredFetoEnum Nuget GitHub Repo stars 2023-10-12

3 EnumClass Nuget GitHub Repo stars 2023-08-08

4 EnumsEnhanced Nuget GitHub Repo stars 2025-08-05

5 EnumUtilities Nuget GitHub Repo stars 2024-04-05

6 Flaggen Nuget GitHub Repo stars 2025-07-23

7 FusionReactor Nuget GitHub Repo stars 2024-04-06

8 Genbox.FastEnum Nuget GitHub Repo stars 2025-08-03

9 jos.enumeration Nuget GitHub Repo stars 2025-07-20

10 LinkDotNet.Enumeration Nuget GitHub Repo stars 2026-05-14

11 NetEscapades.EnumGenerators Nuget GitHub Repo stars 2023-04-16

12 PMart.Enumeration NugetNuget GitHub Repo stars 2025-03-25

13 Porticle.Enumly Nuget GitHub Repo stars 2026-07-02

14 RapidEnum Nuget GitHub Repo stars 2025-10-04

15 requiredenum Nuget GitHub Repo stars 2025-08-14

16 TaggedEnum Nuget GitHub Repo stars 2026-04-05

See category

Enum