FractalDataWorks.EnhancedEnums.SourceGenerator
0.1.46-alpha-g4eea8a7f8d
dotnet add package FractalDataWorks.EnhancedEnums.SourceGenerator --version 0.1.46-alpha-g4eea8a7f8d
NuGet\Install-Package FractalDataWorks.EnhancedEnums.SourceGenerator -Version 0.1.46-alpha-g4eea8a7f8d
<PackageReference Include="FractalDataWorks.EnhancedEnums.SourceGenerator" Version="0.1.46-alpha-g4eea8a7f8d"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="FractalDataWorks.EnhancedEnums.SourceGenerator" Version="0.1.46-alpha-g4eea8a7f8d" />
<PackageReference Include="FractalDataWorks.EnhancedEnums.SourceGenerator"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add FractalDataWorks.EnhancedEnums.SourceGenerator --version 0.1.46-alpha-g4eea8a7f8d
#r "nuget: FractalDataWorks.EnhancedEnums.SourceGenerator, 0.1.46-alpha-g4eea8a7f8d"
#:package FractalDataWorks.EnhancedEnums.SourceGenerator@0.1.46-alpha-g4eea8a7f8d
#addin nuget:?package=FractalDataWorks.EnhancedEnums.SourceGenerator&version=0.1.46-alpha-g4eea8a7f8d&prerelease
#tool nuget:?package=FractalDataWorks.EnhancedEnums.SourceGenerator&version=0.1.46-alpha-g4eea8a7f8d&prerelease
FractalDataWorks Enhanced Enums
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
- Installation
- Package Types
- Quick Start
- Single Assembly Setup
- Cross-Assembly Setup
- Manual Usage
- Decision Trees
- Complete Examples
- API Reference
- Analyzers & Code Fixes
- Breaking Changes
- Sample Projects
- Debugging & Development
- Smart Generators Framework
- Troubleshooting
Overview
Enhanced Enums provides a modular architecture with three specialized packages:
- FractalDataWorks.EnhancedEnums - Core models, services, and builders (shared library)
- FractalDataWorks.EnhancedEnums.SourceGenerator - Single-assembly source generator
- 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
- Visual Studio: Click the lightbulb icon when the warning appears
- VS Code: Use Quick Fix (Ctrl+.)
- 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 totrue
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:
- SetupSample.ps1: Builds and packs the individual sample
- 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<BaseType>]
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 classGenerateFactoryMethods
(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. Usetypeof(IInterface)
Namespace
(string): Target namespace for generated classDefaultGenericReturnType
(Type): Return type for generic basesIncludeReferencedAssemblies
(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 methodAllowMultiple
(bool): Allow multiple results. Default:false
- When
false
: Returns single nullable result, uses Dictionary for O(1) lookups - When
true
: ReturnsImmutableArray<T>
with all matching results, uses Dictionary<TKey, ImmutableArray<T>>
- When
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
:
- The analyzer
ENHENUM001
will show a warning - A code fix is available to automatically add
AllowMultiple = true
- The generator will create appropriate collection structures (Dictionary vs Dictionary<key, ImmutableArray>)
Performance Notes:
- Single lookups use
FrozenDictionary
(.NET 8+) orImmutableDictionary
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
Check base class configuration:
[EnumCollection( CollectionName = "Colors", IncludeReferencedAssemblies = true)] // Required!
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" />
Check option packages:
// Must have [EnumOption] [EnumOption] public class Red : ColorOptionBase { ... }
Check assembly metadata (for advanced debugging):
<ItemGroup> <AssemblyMetadata Include="IncludeInEnhancedEnumAssemblies" Value="*" /> </ItemGroup>
Factory Methods Not Generated
Check GenerateFactoryMethods setting:
[EnumCollection( CollectionName = "Statuses", GenerateFactoryMethods = true)] // Must be true
Check for parameterless constructor:
[EnumOption] public class Active : StatusBase { public Active() : base(1, "Active") { } // Required! }
Check inheritance:
// Must inherit from [EnumCollection] marked class public class Active : StatusBase // StatusBase has [EnumCollection]
Generic Collections Not Working
Check Generic property:
[EnumCollection( CollectionName = "Animals", Generic = true, GenerateStaticCollection = false)] // Instance-based for generic collections
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
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 | Versions 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. |
-
.NETStandard 2.0
- ILRepack.MSBuild.Task (>= 2.0.13)
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 |
---|