Buseco.Core.Polymorphic
10.0.1
dotnet add package Buseco.Core.Polymorphic --version 10.0.1
NuGet\Install-Package Buseco.Core.Polymorphic -Version 10.0.1
<PackageReference Include="Buseco.Core.Polymorphic" Version="10.0.1" />
<PackageVersion Include="Buseco.Core.Polymorphic" Version="10.0.1" />
<PackageReference Include="Buseco.Core.Polymorphic" />
paket add Buseco.Core.Polymorphic --version 10.0.1
#r "nuget: Buseco.Core.Polymorphic, 10.0.1"
#:package Buseco.Core.Polymorphic@10.0.1
#addin nuget:?package=Buseco.Core.Polymorphic&version=10.0.1
#tool nuget:?package=Buseco.Core.Polymorphic&version=10.0.1
PolyEnum
PolyEnum is a small pattern/library for mapping a regular C# enum value to a polymorphic implementation that is resolved through Dependency Injection.
Design goals
PolyEnum is designed around the following goals:
- keep plain enums where enums are already useful
- move behavior into separate polymorphic types
- support DI natively
- avoid runtime AppDomain scanning
- avoid fragile name-based mapping
- make implementation replacement easy
- keep composition in the startup layer
Design choices
Explicit registration over AppDomain scanning Earlier approaches often rely on scanning all loaded assemblies and matching types by convention. PolyEnum prefers explicit startup registration because it is:
Instead of putting behavior directly into a Smart Enum type, PolyEnum keeps the enum as the stable identity and resolves the behavior through DI at runtime.
This makes enum-driven behavior:
- easier to compose
- easier to replace
- easier to test
- easier to integrate with infrastructure dependencies such as logging, clocks, options, repositories, policies, or other services
Why this exists
In C#, a regular enum is still just a named set of constants.
It cannot define methods inside the enum declaration itself, and additional behavior usually ends up in extension methods, helper classes, or switch statements spread around the codebase.
Enum
The Smart Enum pattern is a well-known way to improve this by replacing primitive enums with richer object-oriented types that can carry both data and behavior. Popular implementations such as Ardalis.SmartEnum and Thinktecture's Smart Enum approach explicitly position Smart Enums as a more expressive and type-safe alternative to traditional enums. Ardalis SmartEnum Smart Enums: Beyond Traditional Enumerations in .NET Meziantou's Blog
However, classic Smart Enum implementations are often based on predefined static instances on the main type. That works very well when the enum-like value is itself the domain object, but it can become limiting when the behavior for a value needs external dependencies or needs to be replaced through composition instead of changing the main type.
PolyEnum is designed for those cases.
Core idea
PolyEnum separates three concerns:
- Enum → stable identity
- Handler / implementation class → behavior
- DI registration → mapping between identity and implementation
Instead of hard-coding the implementation for an enum-like value inside a central Smart Enum class, PolyEnum moves that binding into the composition root.
That means you can:
- keep your public contracts and persisted values as plain enums
- implement behavior in separate classes
- inject services into those classes
- replace an implementation by changing startup registration rather than rewriting the core model
When to use PolyEnum
Use PolyEnum when:
- you already have a regular enum and want to keep it
- each enum value should have its own behavior
- those behaviors need Dependency Injection
- implementations may need to be replaced over time
- you want to avoid large
switchstatements - you want to keep infrastructure concerns out of your enum model
Typical examples:
- order states
- payment methods
- document types
- workflow steps
- notification channels
- business strategies selected by enum value
When not to use PolyEnum
PolyEnum is not always the right choice.
You probably do not need it when:
- the enum is just a simple primitive value
- the behavior is trivial
- the behavior is purely intrinsic and deterministic
- you do not need DI
- a classic Smart Enum or even a plain enum is enough
If the value itself is the domain concept and should be modeled as a rich value object, a classic Smart Enum may still be the more elegant choice. Smart Enum libraries are specifically designed for that use case: replacing primitive enum values with richer type-safe objects that carry their own data and behavior. 234
Why not just use Smart Enum?
Smart Enum is a strong pattern, but it solves a slightly different problem.
In a classic Smart Enum design:
- the value and the behavior live in the same type
- values are often represented as static predefined instances
- the binding between value and implementation is typically centralized in that type
That is elegant when the value is the object.
But in many applications, an enum value is really just a key to a strategy, policy, or handler.
In those scenarios, a DI-based mapping has a few advantages:
- implementations can depend on services
- implementations can be replaced without rewriting the core enum model
- composition happens at startup, not inside a central static type
- identity remains a simple enum for persistence, contracts, and serialization
In other words:
- Smart Enum → “the value is the object”
- PolyEnum → “the value selects the object”
Relationship to modern C# language design
C# language design has ongoing work around unions and enhanced enum-like constructs, including proposals for native union support and extended/enhanced enums. These proposals aim to improve modeling of closed sets of alternatives and support richer pattern matching and exhaustiveness checking. Unions Discriminated Unions and Enhanced Enums for C#
Installation
Example package name:
dotnet add package PolyEnum
Register service
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddSingleton<IClock, SystemClock>();
services.AddPolyEnums(poly =>
{
poly.AddCase<OrderState, OrderStateHandler, PendingOrderState>();
poly.AddCase<OrderState, OrderStateHandler, ApprovedOrderState>();
poly.AddCase<OrderState, OrderStateHandler, RejectedOrderState>();
});
var provider = services.BuildServiceProvider();
Resolve types
var resolver = provider.GetRequiredService<IEnumTypeResolver>();
var state = resolver.Get<OrderState, OrderStateHandler>(OrderState.Pending);
Console.WriteLine(state.Name); // Pending
Console.WriteLine(state.Value); // Pending
Console.WriteLine(state.Describe()); // Uses IClock through DI
Console.WriteLine(state.CanTransitionTo(OrderState.Approved)); // True
Recommended usage guidelines
Use PolyEnum when the enum value acts like a selector for behavior.
Avoid PolyEnum when the enum-like value should itself be the domain object.
A useful rule of thumb:
if the value is the object → consider Smart Enum if the value selects the object → consider PolyEnum
Future ideas
Possible future improvements:
- assembly-scanning registration for selected assemblies only
- source generator support for registration
- validation helpers for missing enum registrations
- support for open generic enum families
- analyzer support for registration completeness
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.1 | 111 | 7/28/2026 |