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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Buseco.Core.Polymorphic" Version="10.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Buseco.Core.Polymorphic" Version="10.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Buseco.Core.Polymorphic" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Buseco.Core.Polymorphic --version 10.0.1
                    
#r "nuget: Buseco.Core.Polymorphic, 10.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Buseco.Core.Polymorphic@10.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Buseco.Core.Polymorphic&version=10.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Buseco.Core.Polymorphic&version=10.0.1
                    
Install as a Cake Tool

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 switch statements
  • 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

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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