FractalDataWorks.EnhancedEnums.SourceGenerator 0.1.46-alpha-g4eea8a7f8d

This is a prerelease version of FractalDataWorks.EnhancedEnums.SourceGenerator.
The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package FractalDataWorks.EnhancedEnums.SourceGenerator --version 0.1.46-alpha-g4eea8a7f8d
                    
NuGet\Install-Package FractalDataWorks.EnhancedEnums.SourceGenerator -Version 0.1.46-alpha-g4eea8a7f8d
                    
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="FractalDataWorks.EnhancedEnums.SourceGenerator" Version="0.1.46-alpha-g4eea8a7f8d">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FractalDataWorks.EnhancedEnums.SourceGenerator" Version="0.1.46-alpha-g4eea8a7f8d" />
                    
Directory.Packages.props
<PackageReference Include="FractalDataWorks.EnhancedEnums.SourceGenerator">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
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 FractalDataWorks.EnhancedEnums.SourceGenerator --version 0.1.46-alpha-g4eea8a7f8d
                    
#r "nuget: FractalDataWorks.EnhancedEnums.SourceGenerator, 0.1.46-alpha-g4eea8a7f8d"
                    
#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 FractalDataWorks.EnhancedEnums.SourceGenerator@0.1.46-alpha-g4eea8a7f8d
                    
#: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=FractalDataWorks.EnhancedEnums.SourceGenerator&version=0.1.46-alpha-g4eea8a7f8d&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=FractalDataWorks.EnhancedEnums.SourceGenerator&version=0.1.46-alpha-g4eea8a7f8d&prerelease
                    
Install as a Cake Tool

FractalDataWorks Enhanced Enums

NuGet License

A powerful source generator suite for creating type-safe, object-oriented enumerations in C# with cross-assembly discovery and zero boilerplate.

Table of Contents

Overview

Enhanced Enums provides a modular architecture with three specialized packages:

  1. FractalDataWorks.EnhancedEnums - Core models, services, and builders (shared library)
  2. FractalDataWorks.EnhancedEnums.SourceGenerator - Single-assembly source generator
  3. FractalDataWorks.EnhancedEnums.CrossAssembly - Cross-assembly source generator

Key Features

  • Zero boilerplate: Define enum options as simple classes, get full collections automatically
  • Type-safe: All generated code is strongly typed with compile-time safety
  • Cross-assembly discovery: Find enum options across multiple NuGet packages
  • Flexible patterns: Singleton instances, factory patterns, static/abstract collections
  • Rich lookups: Built-in name/ID lookups plus custom property-based lookups with multiple results support
  • Generic support: Abstract generic collections with type-safe wrappers
  • Performance optimized: Uses FrozenDictionary on .NET 8+, ImmutableDictionary on older versions
  • Built-in analyzers: Detects common issues like duplicate lookup values with code fixes

Package Architecture

graph TB
    subgraph "Core Package"
        A[FractalDataWorks.EnhancedEnums<br/>Models, Services, Builders]
    end
    
    subgraph "Generator Packages"
        B[FractalDataWorks.EnhancedEnums.SourceGenerator<br/>Single Assembly Generator]
        C[FractalDataWorks.EnhancedEnums.CrossAssembly<br/>Cross Assembly Generator]
    end
    
    subgraph "Your Application"
        D[Manual Usage<br/>Direct builder calls]
        E[Single Assembly Scenario<br/>All types in one project]
        F[Cross Assembly Scenario<br/>Types across multiple packages]
    end
    
    A -.-> B
    A -.-> C
    A -.-> D
    
    E --> B
    F --> C
    D --> A

Installation

NuGet Package Manager

# For single-assembly scenarios
Install-Package FractalDataWorks.EnhancedEnums.SourceGenerator

# For cross-assembly scenarios  
Install-Package FractalDataWorks.EnhancedEnums.CrossAssembly

# For manual usage only
Install-Package FractalDataWorks.EnhancedEnums

.NET CLI

# For single-assembly scenarios
dotnet add package FractalDataWorks.EnhancedEnums.SourceGenerator

# For cross-assembly scenarios
dotnet add package FractalDataWorks.EnhancedEnums.CrossAssembly

# For manual usage only
dotnet add package FractalDataWorks.EnhancedEnums

PackageReference


<PackageReference Include="FractalDataWorks.EnhancedEnums.SourceGenerator" Version="*.*.*" />


<PackageReference Include="FractalDataWorks.EnhancedEnums.CrossAssembly" Version="*.*.*" />


<PackageReference Include="FractalDataWorks.EnhancedEnums" Version="*.*.*" />

Package Types

Choose the right package for your scenario:

Package Use Case Generator Location Dependencies
SourceGenerator All enum types in same project Runs in project with base class Base + Options in same assembly
CrossAssembly Enum types across multiple packages Runs in consumer project Base package + Option packages
Core Only Manual builder usage No generator Manual integration only

Requirements:

  • IncludeReferencedAssemblies = true on base class for cross-assembly scenarios
  • Consumer project must reference the generator for cross-assembly
  • Consumer project must reference all option packages for cross-assembly

Quick Start

Single Assembly Example

// Define the base class
[EnumCollection(CollectionName = "OrderStatuses")]
public abstract class OrderStatusBase : EnumOptionBase<OrderStatusBase>
{
    public abstract string Code { get; }
    
    protected OrderStatusBase(int id, string name, string code) : base(id, name)
    {
        Code = code;
    }
}

// Define enum options
[EnumOption]
public class Pending : OrderStatusBase
{
    public Pending() : base(1, "Pending", "PEND") { }
    public override string Code => "PEND";
}

[EnumOption]
public class Shipped : OrderStatusBase
{
    public Shipped() : base(2, "Shipped", "SHIP") { }
    public override string Code => "SHIP";
}

// Generated usage
var all = OrderStatuses.All();
var byName = OrderStatuses.GetByName("Shipped");

Cross-Assembly Example

Base Package (ColorOption.Library):

[EnumCollection(
    CollectionName = "Colors", 
    IncludeReferencedAssemblies = true,  // REQUIRED for cross-assembly
    GenerateFactoryMethods = true)]
public abstract class ColorOptionBase : EnumOptionBase<ColorOptionBase>
{
    public string Hex { get; }
    
    [EnumLookup("GetByValue")]
    public int Value { get; }
    
    protected ColorOptionBase(int id, string name, string hex, int value) : base(id, name)
    {
        Hex = hex;
        Value = value;
    }
}

Option Packages (Red.Library, Blue.Library):

[EnumOption]
public class Red : ColorOptionBase
{
    public Red() : base(1, "Red", "#FF0000", 1) { }
}

[EnumOption]
public class Blue : ColorOptionBase
{
    public Blue() : base(2, "Blue", "#0000FF", 2) { }
}

Consumer Project:

// Generated collection (no code needed!)
var red = Colors.Red();              // Factory method
var blue = Colors.GetByName("Blue"); // Singleton lookup
var byValue = Colors.GetByValue(1);  // Custom lookup
var all = Colors.All();              // All colors

Analyzers & Code Fixes

ENHENUM001: Duplicate Lookup Values

The Enhanced Enums analyzer detects when multiple enum options have the same value for a lookup property without AllowMultiple = true.

Problem:

[EnumCollection(CollectionName = "Animals")]
public abstract class AnimalBase : EnumOptionBase<AnimalBase>
{
    [EnumLookup("GetByClass")]  // ⚠️ ENHENUM001
    public string AnimalClass { get; }
}

[EnumOption]
public class Cat : AnimalBase  
{
    public Cat() : base(1, "Cat", "Mammal") { }  // AnimalClass = "Mammal"
}

[EnumOption]
public class Dog : AnimalBase
{
    public Dog() : base(2, "Dog", "Mammal") { }  // AnimalClass = "Mammal" (duplicate!)
}

Solution (Automatic Code Fix):

[EnumLookup("GetByClass", true)]  // AllowMultiple = true added
public string AnimalClass { get; }

Generated Code Changes:

Without AllowMultiple:

public static AnimalBase? GetByClass(string animalClass)
{
    // Returns single value or null
}

With AllowMultiple = true:

public static ImmutableArray<AnimalBase> GetByClass(string animalClass)
{
    // Returns array of all matching animals
}

Using the Code Fix

  1. Visual Studio: Click the lightbulb icon when the warning appears
  2. VS Code: Use Quick Fix (Ctrl+.)
  3. Command Line: Apply fixes with dotnet format

The code fix automatically:

  • Adds AllowMultiple = true to the attribute
  • Handles both positional and named parameters
  • Updates existing false values to true

Breaking Changes

Note: This library is currently in alpha (v0.1.x). Breaking changes may occur between minor versions.

Recent Changes

Namespace Consolidation

All base types and attributes have been moved to the FractalDataWorks.EnhancedEnums namespace for better self-containment.

Before:

using FractalDataWorks;  // For attributes
using FractalDataWorks.EnhancedEnums;  // For base types

After:

using FractalDataWorks.EnhancedEnums;  // Everything
using FractalDataWorks.EnhancedEnums.Attributes;  // Attributes specifically

This makes the FractalDataWorks.EnhancedEnums package fully self-contained without requiring the base FractalDataWorks package.

All() and Empty() as Methods

The All and Empty members are now methods for consistency and future extensibility.

Usage:

var allColors = Colors.All();  // Method
var count = Colors.All().Length;
foreach (var color in Colors.All()) { ... }

var empty = Colors.Empty();  // Method
New UseSingletonInstances Property

NEW FEATURE: Control whether All() returns the same instances (singleton) or creates new instances (factory pattern).

[EnumCollection(
    CollectionName = "Colors",
    UseSingletonInstances = true)]   // Default: true (singleton pattern)
  • Singleton pattern (UseSingletonInstances = true): All() returns same instances, saves memory
  • Factory pattern (UseSingletonInstances = false): All() creates new instances each call
Generic Collections Support

NEW FEATURE: Generic collections now generate both non-generic base and generic wrapper classes with type-safe filtering.

[EnumCollection(
    CollectionName = "Animals",
    Generic = true)]
public abstract class AnimalBase<T> : EnumOptionBase<T> where T : AnimalBase<T>
{
    // Base implementation
}

Generates:

// When GenerateStaticCollection = true: static non-generic base class
public static class Animals
{
    public static ImmutableArray<AnimalBase> All() { ... }
    public static AnimalBase? GetByName(string name) { ... }
    public static AnimalBase Empty() { ... }
    // ... all methods using AnimalBase
}

// Generic wrapper class with type-safe filtering
public abstract class Animals<T> where T : AnimalBase
{
    public static ImmutableArray<T> All() => Animals.All().OfType<T>().ToImmutableArray();
    public static T? GetByName(string name) => Animals.GetByName(name) as T;
    public static T Empty() => Animals.Empty() as T ?? throw new InvalidOperationException();
    public static ImmutableArray<T> None() => ImmutableArray<T>.Empty; // Like Enumerable.Empty<T>()
    // ... all methods with T return types
}

Usage:

// Non-generic collection - returns all animals
var allAnimals = Animals.All();                    // Returns ImmutableArray<AnimalBase>
var dog = Animals.GetByName("Dog");                // Returns AnimalBase (actually a Dog instance)

// Generic collection - type-safe filtering
var allDogs = Animals<Dog>.All();                  // Returns only Dog instances
var nullCat = Animals<Dog>.GetByName("Cat");       // Returns null (Cat is not a Dog)
var noDogs = Animals<Dog>.None();                  // Returns empty ImmutableArray<Dog>

Sample Projects

The repository includes three cross-assembly sample projects demonstrating different features:

Colors Sample

Location: samples/CrossAssemblyScanner/Colors/

  • Static collection with singleton instances
  • Custom GetByValue lookup
  • Multiple library packages (Red.Library, Blue.Library)
  • Demonstrates basic cross-assembly discovery
var red = ColorCollection.Red();           // Factory method
var blue = ColorCollection.GetByName("Blue");  // Singleton lookup
var primary = ColorCollection.GetByValue(1);   // Custom lookup

Animals Sample

Location: samples/CrossAssemblyScanner/Animals/

  • Static collection with multiple lookup results
  • Demonstrates AllowMultiple for duplicate values
  • Shows analyzer warnings and code fixes
  • GetByHabitat and GetByClass lookups
var mammals = AnimalCollection.GetByClass("Mammal");  // Returns ImmutableArray
foreach (var mammal in mammals)
{
    Console.WriteLine($"{mammal.Name}: {mammal.Species}");
}

Status Sample

Location: samples/CrossAssemblyScanner/Status/

  • Generic collection pattern
  • Multiple status libraries
  • Demonstrates factory vs singleton patterns
  • Custom property lookups
var active = StatusCollection.Active();        // Factory method
var byCode = StatusCollection.GetByCode("ACT");  // Lookup by code

Running the Samples

Each sample includes setup scripts:

  1. SetupSample.ps1: Builds and packs the individual sample
  2. SetupAll.ps1: Builds all libraries and runs the console app
# Build and run a specific sample
cd samples/CrossAssemblyScanner/Animals
./SetupAll.ps1

# Or setup just the packages
./SetupSample.ps1

Single Assembly Setup

Use when all enum types are in the same project.

graph LR
    A[Your Project] --> B[Base Class + Options]
    B --> C[SourceGenerator Package]
    C --> D[Generated Collection]

Installation:

dotnet add package FractalDataWorks.EnhancedEnums.SourceGenerator

Requirements:

  • Base class with [EnumCollection]
  • Option classes with [EnumOption]
  • Both in same project

Cross-Assembly Setup

Use when enum options are distributed across multiple NuGet packages.

graph TB
    subgraph "Base Package"
        A[ColorOptionBase<br/>with [EnumCollection]<br/>IncludeReferencedAssemblies = true]
    end
    
    subgraph "Red.Library Package"
        B[Red class<br/>with [EnumOption]]
    end
    
    subgraph "Blue.Library Package"
        C[Blue class<br/>with [EnumOption]]
    end
    
    subgraph "Consumer Project"
        D[References all packages<br/>+ CrossAssembly generator]
        E[Generated Colors collection]
    end
    
    A -.-> B
    A -.-> C
    D --> A
    D --> B  
    D --> C
    D --> E

Setup Process:

graph LR
    A[Create Base Package] --> B[Create Option Packages]
    B --> C[Setup Consumer Project]
    C --> D[Mark with EnumCollection]
    D --> E[Set IncludeReferencedAssemblies = true]
    E --> F[Pack as NuGet package]
// File: ColorOption.Library/ColorOptionBase.cs
[EnumCollection(
    CollectionName = "Colors",
    IncludeReferencedAssemblies = true,  // CRITICAL for cross-assembly
    GenerateFactoryMethods = true,
    UseSingletonInstances = true,
    NameComparison = StringComparison.OrdinalIgnoreCase)]
public abstract class ColorOptionBase : EnumOptionBase<ColorOptionBase>
{
    public string Hex { get; }
    
    [EnumLookup("GetByValue")]  // Optional: Custom lookup
    public int Value { get; }
    
    protected ColorOptionBase(int id, string name, string hex, int value) : base(id, name)
    {
        Hex = hex;
        Value = value;
    }
}

Consumer Project References:

<PackageReference Include="FractalDataWorks.EnhancedEnums.CrossAssembly" Version="*.*.*" />
<PackageReference Include="ColorOption.Library" Version="1.0.0" />
<PackageReference Include="Red.Library" Version="1.0.0" />
<PackageReference Include="Blue.Library" Version="1.0.0" />

Manual Usage

For advanced scenarios requiring custom integration:

public static class ManualColors
{
    public static string BuildColorCollection()
    {
        var statusTypes = new List<EnumValueInfo>
        {
            new() { Name = "Red", FullTypeName = "MyApp.Red" },
            new() { Name = "Blue", FullTypeName = "MyApp.Blue" }
        };

        var enumDef = new EnumTypeInfo
        {
            ClassName = "ColorBase",
            CollectionName = "Colors", 
            Namespace = "MyApp",
            EnumValues = statusTypes,
            GenerateFactoryMethods = true,
            UseSingletonInstances = true
        };

        // Use the same builders as the source generators
        return EnumCollectionBuilder.BuildCollection(enumDef, "ColorBase");
    }
}

Project Structure:

MyProject/
├── ColorOption.Library/           # Base package
│   ├── ColorOptionBase.cs         # [EnumCollection(IncludeReferencedAssemblies = true)]
│   └── ColorOption.Library.csproj # References: FractalDataWorks
├── Red.Library/                   # Option package
│   ├── Red.cs                     # [EnumOption]
│   └── Red.Library.csproj         # References: ColorOption.Library
├── Blue.Library/                  # Option package
│   ├── Blue.cs                    # [EnumOption]
│   └── Blue.Library.csproj        # References: ColorOption.Library
└── ConsumerApp/                   # Consumer project
    ├── Program.cs                 # Uses Colors.Red(), Colors.All(), etc.
    └── ConsumerApp.csproj         # References: CrossAssembly + all packages

Decision Trees

When to Use Factory Methods

graph TD
    A{Do you need both<br/>singletons and<br/>new instances?}
    
    A -->|Yes| B[Set GenerateFactoryMethods = true<br/>Factory methods create new instances<br/>GetByName() returns singletons]
    
    A -->|No| C{Do you need the<br/>fastest possible<br/>performance?}
    
    C -->|Yes| D[Set GenerateFactoryMethods = false<br/>Use GetByName() for all access<br/>No factory method overhead]
    
    C -->|No| E[Keep default: true<br/>Provides both options]
    
    B --> F[Usage: Colors.Red() creates new<br/>Colors.GetByName('Red') returns singleton]
    D --> G[Usage: Colors.GetByName('Red') only<br/>No Colors.Red() method available]
    E --> H[Usage: Both available<br/>Choose based on need]

When to Use Singleton vs Factory Pattern

graph TD
    A{How do you use<br/>enum instances?}
    
    A -->|Reference comparison<br/>Memory efficiency| B[UseSingletonInstances = true<br/>All() returns same instances<br/>Better for reference equality]
    
    A -->|Mutation required<br/>Independent instances| C[UseSingletonInstances = false<br/>All() creates new instances<br/>Better for modification]
    
    A -->|Mixed usage| D[Default: true<br/>Use GetByName() for singletons<br/>Use factory methods for new instances]

Package Architecture Choice

graph TD
    A{What's your<br/>distribution model?}
    
    A -->|Single project<br/>All types together| B[SourceGenerator Package<br/>FractalDataWorks.EnhancedEnums.SourceGenerator<br/>- Simple setup<br/>- Fast compilation<br/>- Generator runs in base project]
    
    A -->|Multiple NuGet packages<br/>Distributed options| C[CrossAssembly Package<br/>FractalDataWorks.EnhancedEnums.CrossAssembly<br/>- Cross-assembly discovery<br/>- Set IncludeReferencedAssemblies = true<br/>- Consumer project generates collection]
    
    A -->|Custom integration<br/>Manual control| D[Core Package Only<br/>FractalDataWorks.EnhancedEnums<br/>- Manual builder usage<br/>- Custom integration<br/>- No automatic generation]
    
    A -->|Mix of same project<br/>and packages| E[Hybrid Approach<br/>Use CrossAssembly Package<br/>- Base + some options in same project<br/>- Additional options in packages<br/>- Set IncludeReferencedAssemblies = true]
    
    B --> F[Generator runs in<br/>project with base class]
    C --> G[Generator runs in<br/>consumer project only]
    D --> H[Manual control<br/>Custom integration]
    E --> I[Generator runs in<br/>base project, includes external options]

Custom Lookup Decision Tree

graph TD
    A{Do you need custom<br/>property lookups?} 
    
    A -->|No| B[Use built-in methods:<br/>- GetByName(string)<br/>- GetById(int) if EnumOptionBase<br/>- All() method<br/>- Empty() method]
    
    A -->|Yes| C{What type of lookup?}
    
    C -->|Single result| D[Add [EnumLookup('GetByCustom')] to property<br/>Generates: GetByCustom(PropertyType)<br/>Returns: BaseType? (nullable)]
    
    C -->|Multiple results| E[Add [EnumLookup('GetByCustom', AllowMultiple = true)]<br/>Generates: GetByCustom(PropertyType)<br/>Returns: ImmutableArray&lt;BaseType&gt;]
    
    C -->|Custom return type| F[Add [EnumLookup('GetByCustom', ReturnType = typeof(ICustom))]<br/>Generates: GetByCustom(PropertyType)<br/>Returns: ICustom?]
    
    D --> G[Property must be in constructor<br/>or be nullable]
    E --> G
    F --> G
    
    G --> H[Usage: var result = MyEnums.GetByCustom(value)]

Complete Examples

Basic Single Assembly Example

[EnumCollection(
    CollectionName = "OrderStatuses",
    GenerateFactoryMethods = true,
    UseSingletonInstances = true,
    NameComparison = StringComparison.OrdinalIgnoreCase)]
public abstract class OrderStatusBase : EnumOptionBase<OrderStatusBase>
{
    public abstract string Code { get; }
    public abstract bool CanCancel { get; }
    public abstract bool CanShip { get; }
    
    protected OrderStatusBase(int id, string name, string code) : base(id, name)
    {
        Code = code;
    }
}

[EnumOption]
public class Pending : OrderStatusBase
{
    public Pending() : base(1, "Pending", "PEND") { }
    public override string Code => "PEND";
    public override bool CanCancel => true;
    public override bool CanShip => false;
}

[EnumOption]
public class Shipped : OrderStatusBase
{
    public Shipped() : base(2, "Shipped", "SHIP") { }
    public override string Code => "SHIP";
    public override bool CanCancel => false;
    public override bool CanShip => false;
}

// Usage
class Program
{
    static void Main()
    {
        // Factory methods (creates new instances)
        var pending = OrderStatuses.Pending();
        var shipped = OrderStatuses.Shipped();
        
        // Name lookup (returns singletons)
        var status = OrderStatuses.GetByName("Shipped");
        
        // All statuses
        var all = OrderStatuses.All();
        
        // Empty value
        var empty = OrderStatuses.Empty();
        
        // Safe lookups
        if (OrderStatuses.TryGetByName("Unknown", out var found))
        {
            Console.WriteLine($"Found: {found.Name}");
        }
    }
}

Advanced Cross-Assembly Example

Base Package (PluginBase.Library):

[EnumCollection(
    CollectionName = "Plugins",
    IncludeReferencedAssemblies = true,  // Enable cross-assembly
    GenerateFactoryMethods = true,
    UseSingletonInstances = true,
    ReturnType = typeof(IPlugin))]       // Return interface type
public abstract class PluginBase : EnumOptionBase<PluginBase>, IPlugin
{
    public abstract string Version { get; }
    public abstract string Category { get; }
    
    [EnumLookup("GetByCategory", AllowMultiple = true)]
    public string Category { get; }
    
    [EnumLookup("GetByVersion")]
    public string Version { get; }
    
    protected PluginBase(int id, string name, string version, string category) : base(id, name)
    {
        Version = version;
        Category = category;
    }
}

public interface IPlugin
{
    string Name { get; }
    string Version { get; }
    string Category { get; }
}

Option Packages:

// File: Authentication.Plugin/AuthPlugin.cs
[EnumOption]
public class AuthPlugin : PluginBase
{
    public AuthPlugin() : base(1, "Authentication", "2.1.0", "Security") { }
}

// File: Logging.Plugin/LogPlugin.cs  
[EnumOption]
public class LogPlugin : PluginBase
{
    public LogPlugin() : base(2, "Logging", "1.5.2", "Diagnostics") { }
}

Consumer Project Usage:

// All available automatically!
var allPlugins = Plugins.All();
var authPlugin = Plugins.GetByName("Authentication");
var securityPlugins = Plugins.GetByCategory("Security");
var specificVersion = Plugins.GetByVersion("2.1.0");

// Type safety through interface
IPlugin plugin = Plugins.AuthPlugin();
Console.WriteLine($"{plugin.Name} v{plugin.Version}");

Generic Collection Example

[EnumCollection(
    CollectionName = "Animals",
    Generic = true,
    GenerateStaticCollection = false,
    UseSingletonInstances = true)]
public abstract class AnimalBase<T> : EnumOptionBase<T> where T : AnimalBase<T>
{
    public abstract string Species { get; }
    
    [EnumLookup("GetBySpecies")]
    public string Species { get; }
    
    protected AnimalBase(int id, string name, string species) : base(id, name)
    {
        Species = species;
    }
}

[EnumOption]
public class Dog : AnimalBase<Dog>
{
    public Dog() : base(1, "Dog", "Canis familiaris") { }
}

[EnumOption]
public class Cat : AnimalBase<Cat>
{
    public Cat() : base(2, "Cat", "Felis catus") { }
}

// Generated classes:
// 1. Animals (non-generic base with AnimalBase return types)
// 2. Animals<T> (generic wrapper with T return types)

// Usage:
var allAnimals = Animals.All();           // ImmutableArray<AnimalBase>
var allDogs = Animals<Dog>.All();         // ImmutableArray<Dog>
var dog = Animals<Dog>.GetByName("Dog");  // Dog?

API Reference

Attributes

[EnumCollection]

Marks a class as an enhanced enum base type.

Properties:

  • CollectionName (string, required): Name of generated collection class
  • GenerateFactoryMethods (bool): Generate factory methods. Default: true
  • GenerateStaticCollection (bool): Generate static collection class. Default: true
  • Generic (bool): Generate both non-generic and generic collection classes. Default: false
  • UseSingletonInstances (bool): All() returns same instances (true) vs new instances (false). Default: true
  • NameComparison (StringComparison): String comparison for lookups. Default: Ordinal
  • ReturnType (Type): Return type for methods. Use typeof(IInterface)
  • Namespace (string): Target namespace for generated class
  • DefaultGenericReturnType (Type): Return type for generic bases
  • IncludeReferencedAssemblies (bool): Enable cross-assembly discovery. Default: false

Example:

[EnumCollection(
    CollectionName = "Colors",
    GenerateFactoryMethods = true,
    GenerateStaticCollection = true,
    Generic = false,
    UseSingletonInstances = true,
    NameComparison = StringComparison.OrdinalIgnoreCase,
    ReturnType = typeof(IColor),
    IncludeReferencedAssemblies = true)]
[EnumOption]

Marks a class as an enum option.

Requirements:

  • Must inherit from a class marked with [EnumCollection]
  • Must have parameterless constructor
  • Constructor must call base constructor with id and name
[EnumLookup]

Generates custom lookup methods for properties.

Parameters:

  • methodName (string, required): Name of generated method
  • AllowMultiple (bool): Allow multiple results. Default: false
    • When false: Returns single nullable result, uses Dictionary for O(1) lookups
    • When true: Returns ImmutableArray<T> with all matching results, uses Dictionary<TKey, ImmutableArray<T>>
  • ReturnType (Type): Custom return type (interface or base class)

Single Value Lookup (default):

[EnumLookup("GetByCode")]
public string Code { get; }

// Generated method:
public static StatusBase? GetByCode(string code) { ... }

// Usage:
var active = Statuses.GetByCode("ACT");  // Returns single match or null

Multiple Value Lookup:

[EnumLookup("GetByCategory", AllowMultiple = true)]
public string Category { get; }

// Generated method:
public static ImmutableArray<StatusBase> GetByCategory(string category) { ... }

// Usage:
var systems = Statuses.GetByCategory("System");  // Returns all matches
foreach (var status in systems) { ... }

Duplicate Value Handling:

If multiple enum options have the same value for a lookup property without AllowMultiple = true:

  1. The analyzer ENHENUM001 will show a warning
  2. A code fix is available to automatically add AllowMultiple = true
  3. The generator will create appropriate collection structures (Dictionary vs Dictionary<key, ImmutableArray>)

Performance Notes:

  • Single lookups use FrozenDictionary (.NET 8+) or ImmutableDictionary for O(1) access
  • Multiple lookups use grouped dictionaries with ImmutableArray values
  • All lookups are case-insensitive by default (configurable via NameComparison)

Generated Collection Classes

Static Collection (default)
[EnumCollection(CollectionName = "Statuses")]  // Generic = false, GenerateStaticCollection = true
public static class Statuses
{
    // Methods
    public static ImmutableArray<StatusBase> All() { ... }
    public static StatusBase Empty() { ... }
    
    // Built-in lookups
    public static StatusBase? GetByName(string name);
    public static bool TryGetByName(string name, out StatusBase? result);
    public static StatusBase? GetById(int id);  // If using EnumOptionBase
    
    // Custom lookups (from [EnumLookup])
    public static StatusBase? GetByCode(string code);
    public static ImmutableArray<StatusBase> GetByCategory(string category);
    
    // Factory methods (if GenerateFactoryMethods = true)
    public static StatusBase Pending();
    public static StatusBase Active();
}
Generic Collection
[EnumCollection(CollectionName = "Animals", Generic = true)]
// Generates TWO classes:

// Non-generic base class with all implementation
public abstract class Animals
{
    public static ImmutableArray<AnimalBase> All() { ... }
    public static AnimalBase? GetByName(string name) { ... }
    public static AnimalBase Dog() { ... }
    // ... all methods with AnimalBase return types
}

// Generic wrapper class
public abstract class Animals<T> where T : AnimalBase
{
    public static ImmutableArray<T> All() => Animals.All().OfType<T>().ToImmutableArray();
    public static T? GetByName(string name) => Animals.GetByName(name) as T;
    public static T Dog() => Animals.Dog() as T;
    public static T Empty() => Animals.Empty() as T ?? throw new InvalidOperationException();
    public static ImmutableArray<T> None() => ImmutableArray<T>.Empty;  // Like Enumerable.Empty<T>()
    // ... all methods with T return types
}
Instance Collection
[EnumCollection(CollectionName = "Statuses", GenerateStaticCollection = false)]
public class Statuses  // Non-static, non-generic
{
    // Same methods as static, but as instance methods
    public ImmutableArray<StatusBase> All() { get; }
    public StatusBase Empty() { ... }
    
    // Built-in lookups
    public T? GetByName(string name);
    public bool TryGetByName(string name, out T? result);
    public T? GetById(int id);  // If using EnumOptionBase
    
    // Custom lookups (from [EnumLookup])
    public T? GetByCode(string code);
    public ImmutableArray<T> GetByCategory(string category);
}

Singleton vs Factory Patterns

Singleton Pattern (UseSingletonInstances = true)
// All() returns the same instances every call
var first = Colors.All();
var second = Colors.All();
ReferenceEquals(first[0], second[0]); // true

// GetByName() returns singletons
var red1 = Colors.GetByName("Red");
var red2 = Colors.GetByName("Red");
ReferenceEquals(red1, red2); // true

// Factory methods return singletons (same as GetByName)
var red3 = Colors.Red();
ReferenceEquals(red1, red3); // true
Factory Pattern (UseSingletonInstances = false)
// All() creates new instances every call
var first = Colors.All();
var second = Colors.All();
ReferenceEquals(first[0], second[0]); // false

// GetByName() creates new instances
var red1 = Colors.GetByName("Red");
var red2 = Colors.GetByName("Red");
ReferenceEquals(red1, red2); // false

// Factory methods create new instances
var red3 = Colors.Red();
ReferenceEquals(red1, red3); // false

Performance Characteristics

Pattern Memory Usage Method Calls Reference Equality Use Case
Singleton Lower Faster (cached) Reliable Immutable enums, comparisons
Factory Higher Slightly slower Not reliable Mutable instances, isolation

Debugging & Development

Generated Code Inspection

Enable generated code output in your project:

<PropertyGroup>
    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
    <CompilerGeneratedFilesOutputPath>GeneratedFiles</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

Generated files appear in:

  • obj/Debug/net8.0/generated/
  • GeneratedFiles/ (if configured)

Build-time Debugging

The generators output detailed debug information:

// Check generated debug files:
// - *_Debug.cs: Generation process details
// - *_DiscoveryDebug.cs: Type discovery information  
// - *_LookupDebug.cs: Lookup method generation details

Testing Generated Code

Use the provided testing utilities:

[Fact]
public void ShouldGenerateExpectedCollection()
{
    var generator = new TestSourceGenerator()
        .WithSource(@"
            [EnumCollection(CollectionName = ""Colors"")]
            public abstract class ColorBase : EnumOptionBase<ColorBase>
            {
                protected ColorBase(int id, string name) : base(id, name) { }
            }
            
            [EnumOption]
            public class Red : ColorBase
            {
                public Red() : base(1, ""Red"") { }
            }")
        .AddGenerator<EnhancedEnumGenerator>()
        .WithExpectedClass("Colors", c => c
            .IsPublic()
            .IsStatic()
            .HasMethod("All", m => m
                .IsPublic()
                .IsStatic()
                .HasReturnType("ImmutableArray<ColorBase>"))
            .HasMethod("GetByName", m => m
                .IsPublic()
                .IsStatic()
                .HasReturnType("ColorBase")
                .HasParameter("string", "name")))
        .Verify();
}

Unit Testing Examples

[Fact]
public void GeneratesStaticCollectionClass() { ... }

[Fact]
public void GeneratesGenericCollectionClass() { ... }

[Fact]
public void GeneratesFactoryMethods() { ... }

[Fact]
public void GeneratesCustomLookupMethods() { ... }

[Fact]
public void HandlesCrossAssemblyDiscovery() { ... }

Troubleshooting

Common Issues Decision Tree

graph TD
    A[Build Error or<br/>Missing Generation] --> B{Check generator reference}
    B -->|Missing| C[Add correct package:<br/>SourceGenerator or CrossAssembly]
    B -->|Present| D{Check attributes}
    D -->|Missing [EnumCollection]| E[Add [EnumCollection] to base class<br/>with CollectionName]
    D -->|Missing [EnumOption]| F[Add [EnumOption] to option classes]
    D -->|Correct| G{Check compilation}
    
    G[Issue: Cross-assembly not working] --> H{Check IncludeReferencedAssemblies}
    H -->|False/Missing| I[Set IncludeReferencedAssemblies = true<br/>on base class]
    H -->|True| J{Check consumer project}
    J -->|Missing generator| K[Add FractalDataWorks.EnhancedEnums.CrossAssembly<br/>to consumer project as Analyzer]
    J -->|Missing packages| L[Add all option packages<br/>to consumer project]
    
    M[Issue: Methods not generated] --> N{Check EnumOption attributes}
    N -->|Missing| O[Add [EnumOption] to all option classes]
    N -->|Present| P{Check constructors}
    P -->|No parameterless constructor| Q[Add parameterless constructor<br/>that calls base(id, name)]
    P -->|Correct| R{Check build output}
    R --> S[Check GeneratedFiles folder<br/>for compilation errors]

Cross-Assembly Not Working

  1. Check base class configuration:

    [EnumCollection(
        CollectionName = "Colors",
        IncludeReferencedAssemblies = true)]  // Required!
    
  2. Check consumer project references:

    
    <PackageReference Include="FractalDataWorks.EnhancedEnums.CrossAssembly" Version="*.*.*" />
    
    <PackageReference Include="ColorOption.Library" Version="1.0.0" />
    
    <PackageReference Include="Red.Library" Version="1.0.0" />
    <PackageReference Include="Blue.Library" Version="1.0.0" />
    
  3. Check option packages:

    // Must have [EnumOption]
    [EnumOption]
    public class Red : ColorOptionBase { ... }
    
  4. Check assembly metadata (for advanced debugging):

    
    <ItemGroup>
      <AssemblyMetadata Include="IncludeInEnhancedEnumAssemblies" Value="*" />
    </ItemGroup>
    

Factory Methods Not Generated

  1. Check GenerateFactoryMethods setting:

    [EnumCollection(
        CollectionName = "Statuses",
        GenerateFactoryMethods = true)]  // Must be true
    
  2. Check for parameterless constructor:

    [EnumOption]
    public class Active : StatusBase
    {
        public Active() : base(1, "Active") { }  // Required!
    }
    
  3. Check inheritance:

    // Must inherit from [EnumCollection] marked class
    public class Active : StatusBase  // StatusBase has [EnumCollection]
    

Generic Collections Not Working

  1. Check Generic property:

    [EnumCollection(
        CollectionName = "Animals",
        Generic = true,
        GenerateStaticCollection = false)]  // Instance-based for generic collections
    
  2. Check constraint compatibility:

    // T must be compatible with base type
    public abstract class AnimalBase<T> : EnumOptionBase<T> 
        where T : AnimalBase<T>  // Self-referencing constraint required
    
  3. Usage verification:

    // Both should work
    var base = Animals.All();        // ImmutableArray<AnimalBase>
    var typed = Animals<Dog>.All();  // ImmutableArray<Dog>
    

Quick Checklist

Cross-Assembly Checklist
  • Base package: Has IncludeReferencedAssemblies = true
  • Option packages: Reference base package
  • Option packages: Have [EnumOption] classes
  • Consumer project: References generator as Analyzer
  • Consumer project: References all option packages
  • Consumer project: References base package
Single Assembly Checklist
  • Project: References SourceGenerator package
  • Base class: Has [EnumCollection] with CollectionName
  • Option classes: Have [EnumOption] attribute
  • Option classes: Have parameterless constructor
  • Option classes: Inherit from base class
  • Build: No compilation errors in generated code
Performance Checklist
  • Use UseSingletonInstances = true for memory efficiency
  • Use GenerateFactoryMethods = false if only need lookups
  • Use NameComparison = StringComparison.Ordinal for speed
  • Consider generic collections for type safety without casting

Smart Generators Framework

Enhanced Enums is built on the Smart Generators framework, providing additional utilities:

CodeBuilders API

Building Collection Classes
var collectionClass = new ClassBuilder("OrderStatuses")
    .MakePublic()
    .MakeStatic()
    
    .AddProperty("ImmutableArray<OrderStatusBase>", "All", p => p
        .MakePublic()
        .MakeStatic()
        .MakeReadOnly()
        .WithInitializer("BuildAllArray()"))
    
    .AddMethod("OrderStatus", "GetByName", m => m
        .MakePublic()
        .MakeStatic()
        .AddParameter("string", "name")
        .WithBody(@"
            if (_nameDict.TryGetValue(name, out var result))
                return result;
            return Empty;"))
Building Enum Classes
var enumClass = new ClassBuilder("PendingStatus")
    .MakePublic()
    .WithBaseType("OrderStatusBase")
    .AddAttribute("EnumOption")
    
    .AddConstructor(c => c
        .MakePublic()
        .WithBody("base(1, \"Pending\")"))
    
    .AddProperty("string", "Code", p => p
        .MakePublic()
        .MakeOverride()
        .WithGetter("\"PEND\""))
Advanced Builder Features
var advancedClass = new ClassBuilder("Repository")
    .MakePublic()
    .AddGenericParameter("T")
    .AddGenericConstraint("T", "class, IEntity")
    .WithInterface("IRepository<T>")
    .WithNamespace("MyApp.Data")
    
    // Fields
    .AddField("DbContext", "_context", f => f.MakePrivate().MakeReadOnly())
    
    // Properties with full accessors
    .AddProperty("IQueryable<T>", "Items", p => p
        .MakePublic()
        .WithGetter("_context.Set<T>().AsQueryable()"))
    
    // Methods with complex signatures
    .AddMethod("Task<T?>", "FindAsync", m => m
        .MakePublic()
        .MakeAsync()
        .AddGenericParameter("TKey")
        .AddParameter("TKey", "id")
        .AddParameter("CancellationToken", "cancellationToken", "default")
        .WithBody(@"
            return await _context.Set<T>()
                .FindAsync(new object[] { id }, cancellationToken);"))
    
    // Events
    .AddEvent("EventHandler<T>", "ItemChanged", e => e.MakePublic())
    
    // Nested types
    .AddNestedClass("Builder", nested => nested
        .MakePublic()
        .AddMethod("Repository<T>", "Build", m => m.MakePublic()))

Helper Extensions

// String and naming utilities
var camelCase = "PascalCase".ToCamelCase();        // "pascalCase"
var pluralized = "Status".Pluralize();            // "Statuses"
var normalized = "My_Property-Name".ToValidIdentifier(); // "MyPropertyName"

// Type utilities
var isNullable = type.IsNullableReferenceType();
var baseType = type.GetNonNullableType();
var displayName = type.GetFriendlyName();

// Code generation helpers
var indent = CodeHelper.GetIndentString(3);        // "            " (3 levels)
var comment = CodeHelper.CreateXmlComment("Summary text");
var attribute = CodeHelper.CreateAttribute("ObsoleteAttribute", "\"Use NewMethod instead\"");

Testing Utilities

Generator Testing
[Fact]
public void ShouldGenerateValidCollectionClass()
{
    var result = new TestSourceGenerator()
        .WithGlobalUsings("System", "System.Collections.Generic")
        .WithSource("TestInput.cs", @"
            [EnumCollection(CollectionName = ""TestCollection"")]
            public abstract class TestBase : EnumOptionBase<TestBase>
            {
                protected TestBase(int id, string name) : base(id, name) { }
            }")
        .AddGenerator<MyCustomGenerator>()
        .WithReference<ImmutableArray<int>>()  // Add required references
        .Compile();

    result.ShouldCompileSuccessfully();
    result.ShouldContainClass("TestCollection");
}
Output Validation
[Fact]
public void GeneratedCodeShouldHaveExpectedStructure()
{
    var generator = new TestSourceGenerator()
        .WithSource(InputCode)
        .AddGenerator<EnhancedEnumGenerator>()
        .WithExpectedClass("Colors", expectations => expectations
            .IsPublic()
            .IsStatic()
            .HasProperty("All", p => p
                .IsPublic()
                .IsStatic()
                .HasType("ImmutableArray<ColorBase>"))
            .HasMethod("GetByName", m => m
                .IsPublic()
                .IsStatic()
                .HasReturnType("ColorBase")
                .HasParameters("string name")))
        .WithExpectedOutput(output => output
            .ShouldContain("public static class Colors")
            .ShouldContain("public static ImmutableArray<ColorBase> All()")
            .ShouldNotContain("// TODO"))
        .Verify();
}
Complex Scenarios
[Fact]
public void ShouldHandleGenericConstraints()
{
    var test = new TestSourceGenerator()
        .WithSource(@"
            [EnumCollection(CollectionName = ""Items"", Generic = true)]
            public abstract class ItemBase<T> : EnumOptionBase<T> 
                where T : ItemBase<T>, IComparable<T>
            {
                protected ItemBase(int id, string name) : base(id, name) { }
            }")
        .AddGenerator<EnhancedEnumGenerator>()
        .WithExpected(expectations => expectations
            .HasClass("Items", c => c
                .IsPublic()
                .IsAbstract()
                .HasNoGenericParameters())
            .HasClass("Items", c => c
                .IsPublic()  
                .IsAbstract()
                .HasGenericParameter("T")
                .HasConstraint("T", "where T : ItemBase<T>, IComparable<T>")
                .HasBaseType("Items")))
        .Verify();
}

For more information, visit the GitHub repository or check the API documentation.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on FractalDataWorks.EnhancedEnums.SourceGenerator:

Package Downloads
FractalDataWorks.Services.DataProviders.Abstractions

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated