FractalDataWorks.SourceGenerators 0.4.0-preview.6

This is a prerelease version of FractalDataWorks.SourceGenerators.
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.SourceGenerators --version 0.4.0-preview.6
                    
NuGet\Install-Package FractalDataWorks.SourceGenerators -Version 0.4.0-preview.6
                    
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.SourceGenerators" Version="0.4.0-preview.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FractalDataWorks.SourceGenerators" Version="0.4.0-preview.6" />
                    
Directory.Packages.props
<PackageReference Include="FractalDataWorks.SourceGenerators" />
                    
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.SourceGenerators --version 0.4.0-preview.6
                    
#r "nuget: FractalDataWorks.SourceGenerators, 0.4.0-preview.6"
                    
#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.SourceGenerators@0.4.0-preview.6
                    
#: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.SourceGenerators&version=0.4.0-preview.6&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=FractalDataWorks.SourceGenerators&version=0.4.0-preview.6&prerelease
                    
Install as a Cake Tool

FractalDataWorks.SourceGenerators

Core infrastructure for building source generators in the FractalDataWorks framework. This package provides reusable builders, generators, and models that form the foundation for all specialized source generator implementations (TypeCollections, ServiceTypes, Messages).

Overview

FractalDataWorks.SourceGenerators implements the Gang of Four Builder pattern to orchestrate complex code generation scenarios. It provides a modular architecture where specialized generators handle specific concerns (fields, methods, constructors, properties) and a director coordinates their execution.

Target Framework

  • netstandard2.0 - Required for Roslyn source generator compatibility

Why netstandard2.0 Only (No Multi-Targeting)

Source generator projects MUST target only netstandard2.0. Multi-targeting is not supported for source generators.

Correct:

<TargetFramework>netstandard2.0</TargetFramework>

INCORRECT (will fail):

<TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
Why Single-Target Only?
  1. Roslyn Host Constraint - The C# compiler (Roslyn) loads source generators into its own process, which expects netstandard2.0 assemblies. Multi-targeting would create multiple output folders (netstandard2.0/, net8.0/, net10.0/) and Roslyn wouldn't know which to load.

  2. Analyzer Loading - When referenced as an analyzer:

    <ProjectReference Include="..." OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
    

    The build system specifically looks for the analyzer DLL in the netstandard2.0 output path. Multi-targeting breaks this lookup.

  3. Package Structure - Source generators are packaged in analyzers/dotnet/cs/ directory, not the standard lib/ directory. Multi-targeting doesn't apply to analyzers.

Compatibility

Despite targeting netstandard2.0, source generators work with all .NET versions:

  • ✅ .NET Framework 4.7.2+
  • ✅ .NET Core 3.1+
  • ✅ .NET 5, 6, 7, 8
  • ✅ .NET 10.0+

The generated code adapts to the consumer's target framework, not the generator's target framework.

Framework's Directory.Build.props Configuration

The FractalDataWorks framework uses Directory.Build.props to configure common settings for all projects. Note that multi-targeting is NOT automatically applied; each project specifies its own target framework.

From Directory.Build.props:

<PropertyGroup>
  <LangVersion>preview</LangVersion>
  <Nullable>enable</Nullable>
  <ImplicitUsings>disable</ImplicitUsings>
  <Configurations>Debug;Release;Experimental;Alpha;Beta;Preview;Refactor;Test</Configurations>
</PropertyGroup>
How It Works

Project-Specific Targeting:

  • Each project explicitly sets its <TargetFramework> or <TargetFrameworks> in its .csproj
  • Source generators and abstractions typically use netstandard2.0 only
  • Services and applications typically use net10.0 only

Override Examples:


<PropertyGroup>
  <TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>


<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
</PropertyGroup>


<PropertyGroup>
  <TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
</PropertyGroup>
Polyfills for netstandard2.0

The framework automatically adds polyfill packages for modern C# features on netstandard2.0.

From Directory.Build.props:147-152:

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
  <PackageReference Include="IsExternalInit" PrivateAssets="all">
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>
  <PackageReference Include="PolySharp" PrivateAssets="all" />
</ItemGroup>

What This Enables:

  • init accessors on properties
  • record types
  • required members (with PolySharp)
  • Pattern matching enhancements
  • Other modern C# syntax on older frameworks

Multi-Targeting for Runtime Libraries

While source generators must be netstandard2.0 only, runtime libraries in the framework use the Directory.Build.props defaults (netstandard2.0;net10.0) unless overridden:

Abstractions Pattern

Abstractions projects typically target netstandard2.0 only for maximum compatibility:


<TargetFramework>netstandard2.0</TargetFramework>

Why: Abstractions define contracts (interfaces, base classes, attributes). These are referenced by both:

  • Source generators (require netstandard2.0)
  • Implementation projects (may target newer frameworks)

Using netstandard2.0 ensures source generators can reference the abstractions.

Implementation Pattern

Implementation projects may use multi-targeting to leverage modern features:


<TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>

Why: Implementations can optimize for newer frameworks while maintaining backwards compatibility:

#if NET8_0_OR_GREATER
    // Use .NET 8 FrozenDictionary with alternate key lookup
    private static readonly FrozenDictionary<int, IMyType> _byId = values.ToFrozenDictionary(v => v.Id);
#else
    // Fall back to Dictionary for netstandard2.0
    private static readonly Dictionary<int, IMyType> _byId = new(values);
#endif
Service/Application Pattern

Service and application projects target specific modern frameworks:


<TargetFramework>net10.0</TargetFramework>

Why: These are not libraries meant to be consumed by source generators. They can use the latest language features and APIs.

Multi-Targeting Decision Matrix

Project Type Target Framework(s) Reasoning
Source Generators netstandard2.0 Roslyn requirement, single target only
Abstractions netstandard2.0 Maximum compatibility, referenced by generators
Analyzers netstandard2.0 Roslyn requirement, single target only
CodeFixes netstandard2.0 Roslyn requirement, single target only
Runtime Libraries netstandard2.0;net8.0;net10.0 Optimization + compatibility
Services net10.0 Latest features, not consumed by generators
Applications net10.0 Latest features, final executable

Multi-Targeting Example

Here's a complete example showing proper multi-targeting for a runtime library:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
    <LangVersion>preview</LangVersion>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    
    <PackageReference Include="FractalDataWorks.Results" />
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
    
    <PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
    <PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'net8.0' OR '$(TargetFramework)' == 'net10.0'">
    
  </ItemGroup>
</Project>

Conditional Compilation Symbols

When multi-targeting, use conditional compilation to optimize per framework. The generated code from TypeCollection source generators uses this pattern to select optimal dictionary implementations:

// Illustrative pattern - actual implementation is in generated code
#if NET8_0_OR_GREATER
    // Use modern FrozenDictionary for optimized immutable lookups
    private static readonly FrozenDictionary<int, TInterface> _all = values.ToFrozenDictionary(v => v.Id);
#else
    // Fall back to Dictionary for netstandard2.0
    private static readonly Dictionary<int, TInterface> _all = values.ToDictionary(v => v.Id);
#endif

See GenericCollectionBuilder.Build() for the actual generation logic that produces framework-specific code.

Common Multi-Targeting Symbols

Symbol Target Frameworks Use Case
NETSTANDARD2_0 netstandard2.0 Legacy compatibility code
NET8_0_OR_GREATER .NET 8.0+ FrozenDictionary, required members
NET10_0_OR_GREATER .NET 10.0+ Latest C# 13 features
NETCOREAPP .NET Core 3.1+ Core-specific APIs

Build Output Structure

Multi-targeting creates separate build outputs:

bin/
  Release/
    netstandard2.0/
      MyLibrary.dll
    net8.0/
      MyLibrary.dll
    net10.0/
      MyLibrary.dll

NuGet packages include all targets, and the .NET SDK automatically selects the best match for each consumer.

Progressive Build Configurations

The framework defines 6 progressive build configurations in Directory.Build.props, each with increasing quality enforcement:

Configuration Optimize Analyzers Warnings as Errors Use Case
Debug ❌ No ❌ Disabled ❌ No Fast iteration, debugging
Experimental ❌ No ✅ Minimal ❌ No Trying new features
Alpha ✅ Yes ✅ Minimal ❌ No Basic validation with optimization
Beta ✅ Yes ✅ Full ✅ Yes Pre-release quality gate
Preview ✅ Yes ✅ Full ✅ Yes Release candidate
Release ✅ Yes ✅ Full ✅ Yes Production ready
Configuration Details

Debug Configuration:

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
  <Optimize>false</Optimize>
  <RunAnalyzers>false</RunAnalyzers>
  <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
  • Fast builds for rapid development
  • No code analysis overhead
  • Warnings don't block builds

Experimental Configuration:

<PropertyGroup Condition="'$(Configuration)' == 'Experimental'">
  <Optimize>false</Optimize>
  <AnalysisLevel>latest-minimum</AnalysisLevel>
  <RunAnalyzers>true</RunAnalyzers>
  <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
  • Minimal analyzer enforcement
  • Helps catch obvious issues early
  • Still allows experimentation

Alpha Configuration:

<PropertyGroup Condition="'$(Configuration)' == 'Alpha'">
  <Optimize>true</Optimize>
  <AnalysisLevel>latest-minimum</AnalysisLevel>
  <RunAnalyzers>true</RunAnalyzers>
  <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
  • Optimized builds with basic analysis
  • Good for performance testing
  • Warnings reported but don't fail build

Beta Configuration:

<PropertyGroup Condition="'$(Configuration)' == 'Beta'">
  <Optimize>true</Optimize>
  <AnalysisLevel>latest-recommended</AnalysisLevel>
  <RunAnalyzers>true</RunAnalyzers>
  <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
  • First configuration that fails on warnings
  • Full analyzer suite enabled
  • Code style enforcement
  • Use before committing production code

Release Configuration:

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <Optimize>true</Optimize>
  <AnalysisLevel>latest-recommended</AnalysisLevel>
  <RunAnalyzers>true</RunAnalyzers>
  <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
  • Production-ready builds
  • Maximum optimization
  • Full validation
  • Required for NuGet package publishing
Analyzer Packages

All projects (except samples and tests) automatically get these analyzers via Directory.Build.props.

From Directory.Build.props:186-192:

<ItemGroup Condition="'$(IsTestProject)' != 'true' AND !$(MSBuildProjectDirectory.Contains('\samples\'))">
  <PackageReference Include="AsyncFixer" PrivateAssets="All" />
  <PackageReference Include="Meziantou.Analyzer" PrivateAssets="All" />
  <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" PrivateAssets="All" />
  <PackageReference Include="Roslynator.Analyzers" PrivateAssets="All" />
  <PackageReference Include="SecurityCodeScan.VS2019" PrivateAssets="All" />
</ItemGroup>

Coverage:

  • AsyncFixer - Async/await best practices
  • Meziantou.Analyzer - General C# best practices
  • Microsoft.VisualStudio.Threading.Analyzers - Threading and async patterns
  • Roslynator.Analyzers - Code quality and refactoring suggestions
  • SecurityCodeScan - Security vulnerability detection
Globally Suppressed Warnings

Some warnings are globally suppressed as design decisions.

From Directory.Build.props:49:

<NoWarn>$(NoWarn);VSTHRD200;CA1716;CA1000;CA1848;CA1510;CA1834;MA0051;MA0048;CA1711;CA1707;NETSDK1195</NoWarn>
Warning Description Why Suppressed
VSTHRD200 Use Async naming convention Framework uses Task suffix strategy
CA1716 Identifiers should not match keywords Acceptable for domain terms
CA1000 Do not declare static members on generic types Required for TypeCollections
CA1848 Use LoggerMessage delegates Framework uses MessageLogging instead
CA1510 Use ArgumentNullException.ThrowIfNull netstandard2.0 doesn't have it
CA1834 Use StringBuilder.Append(char) Negligible performance impact
MA0051 Method too long Acceptable for generators
MA0048 File name must match type name Generic arity variations together
CA1711 Identifiers should not have incorrect suffix Design choice for "Type" suffix
CA1707 Underscores in member names Allows _camelCase for private/protected fields
NETSDK1195 SDK version warnings Suppressed for preview SDK compatibility
Build Command Examples
# Rapid development
dotnet build

# Test with optimization
dotnet build -c Alpha

# Pre-commit validation (fails on warnings)
dotnet build -c Beta

# Production build
dotnet build -c Release

# Build all configurations
dotnet build -c Debug && dotnet build -c Beta && dotnet build -c Release
Recommendation: Progressive Development Workflow
  1. Day-to-day development - Use Debug configuration
  2. Before committing - Run Beta build to catch issues
  3. Before PR - Ensure Release build succeeds
  4. CI/CD pipeline - Always use Release configuration

This progressive approach lets you move fast during development while ensuring production quality before release.

Architecture

Builder Pattern (GoF)

The package implements the classic Builder pattern with two key participants:

  • Builder (IGenericCollectionBuilder, GenericCollectionBuilder) - Constructs collection classes step-by-step
  • Director (GenericCollectionDirector) - Orchestrates the build process and determines generation strategies

Specialized Generators

Individual generators follow the Single Responsibility Principle:

  • EmptyClassGenerator - Generates empty/not-found implementations
  • FieldGenerator - Generates static fields and lookup dictionaries
  • LookupMethodGenerator - Generates lookup methods (ById, ByName, etc.)
  • StaticConstructorGenerator - Generates static constructors with initialization logic
  • ValuePropertyGenerator - Generates static properties for collection values

Configuration

CollectionBuilderConfiguration - Defines collection-specific settings.

From CollectionBuilderConfiguration.cs:9-46:

public sealed class CollectionBuilderConfiguration
{
    /// <summary>
    /// Gets the fully qualified name of the base collection class (e.g., "FractalDataWorks.Collections.TypeCollectionBase").
    /// </summary>
    public string BaseCollectionTypeName { get; init; } = string.Empty;

    /// <summary>
    /// Gets the generic arity of the base collection type (e.g., 1 for TypeCollectionBase<T>, 5 for ServiceTypeCollectionBase<T,T1,T2,T3,T4>).
    /// </summary>
    public int BaseCollectionArity { get; init; }

    /// <summary>
    /// Gets the namespace containing the base collection types (e.g., "FractalDataWorks.Collections").
    /// </summary>
    public string BaseNamespace { get; init; } = string.Empty;

    /// <summary>
    /// Gets the name of the attribute that marks collection types (e.g., "TypeCollection", "ServiceTypeCollection").
    /// </summary>
    public string CollectionAttributeName { get; init; } = string.Empty;

    /// <summary>
    /// Gets the name of the attribute that marks collection value/option types (e.g., "TypeOption", "ServiceTypeOption").
    /// </summary>
    public string ValueAttributeName { get; init; } = string.Empty;

    /// <summary>
    /// Creates a configuration for TypeCollections (FractalDataWorks.Collections).
    /// </summary>
    public static CollectionBuilderConfiguration ForTypeCollections() => new()
    {
        BaseCollectionTypeName = "FractalDataWorks.Collections.TypeCollectionBase",
        BaseCollectionArity = 1,
        BaseNamespace = "FractalDataWorks.Collections",
        CollectionAttributeName = "TypeCollection",
        ValueAttributeName = "TypeOption"
    };

    /// <summary>
    /// Creates a configuration for ServiceTypeCollections (FractalDataWorks.ServiceTypes).
    /// </summary>
    public static CollectionBuilderConfiguration ForServiceTypeCollections() => new()
    {
        BaseCollectionTypeName = "FractalDataWorks.ServiceTypes.ServiceTypeCollectionBase",
        BaseCollectionArity = 5,
        BaseNamespace = "FractalDataWorks.ServiceTypes",
        CollectionAttributeName = "ServiceTypeCollection",
        ValueAttributeName = "ServiceTypeOption"
    };
}

Core Components

IGenericCollectionBuilder

Fluent builder interface for constructing collection classes. The interface is generic to support different ID types (int for Collections, Guid for ServiceTypes).

From IGenericCollectionBuilder.cs:14-91:

/// <typeparam name="TId">The type used for collection IDs (int for Collections, Guid for ServiceTypes).</typeparam>
public interface IGenericCollectionBuilder<TId>
    where TId : struct
{
    IGenericCollectionBuilder<TId> Configure(CollectionGenerationMode mode);
    IGenericCollectionBuilder<TId> WithDefinition(GenericTypeInfoModel<TId> definition);
    IGenericCollectionBuilder<TId> WithValues(IList<GenericValueInfoModel<TId>> values);
    IGenericCollectionBuilder<TId> WithReturnType(string returnType);
    IGenericCollectionBuilder<TId> WithCompilation(Compilation compilation);
    IGenericCollectionBuilder<TId> WithUserClassModifiers(bool isStatic, bool isAbstract);
    string Build();
}

/// <summary>
/// Non-generic type alias for IGenericCollectionBuilder using int IDs.
/// Provides backward compatibility for Collections and Messages generators.
/// </summary>
public interface IGenericCollectionBuilder : IGenericCollectionBuilder<int>
{
}

GenericCollectionDirector

Orchestrates the building process and determines generation strategies. The director is generic to match the builder interface.

From GenericCollectionDirector.cs:15-60:

/// <typeparam name="TId">The type used for collection IDs (int for Collections, Guid for ServiceTypes).</typeparam>
public sealed class GenericCollectionDirector<TId>
    where TId : struct
{
    private readonly IGenericCollectionBuilder<TId> _builder;

    public GenericCollectionDirector(IGenericCollectionBuilder<TId> builder)
    {
        _builder = builder ?? throw new ArgumentNullException(nameof(builder));
    }

    public string ConstructFullCollection(
        GenericTypeInfoModel<TId> definition,
        IList<GenericValueInfoModel<TId>> values,
        string returnType,
        Compilation compilation)
    {
        ValidateParameters(definition, values, returnType, compilation);
        var mode = DetermineGenerationMode(definition);
        return _builder
            .Configure(mode)
            .WithDefinition(definition)
            .WithValues(values)
            .WithReturnType(returnType)
            .WithCompilation(compilation)
            .Build();
    }

    public string ConstructSimplifiedCollection(
        GenericTypeInfoModel<TId> definition,
        IList<GenericValueInfoModel<TId>> values,
        string returnType,
        Compilation compilation);
}

The director automatically determines the appropriate CollectionGenerationMode based on the CollectionStrategy property.

From CollectionGenerationMode.cs:10-42:

  • StaticCollection - Static class with static members (default for read-only collections)
  • InstanceCollection - Singleton pattern with instance members
  • FactoryCollection - Factory methods for creating instances on demand
  • ServiceCollection - Designed for dependency injection scenarios

GenericCollectionBuilder

Concrete implementation that orchestrates specialized generators. The builder initializes and coordinates the individual generators.

From GenericCollectionBuilder.cs:19-52:

public sealed class GenericCollectionBuilder<TId> : IGenericCollectionBuilder<TId>
    where TId : struct
{
    private readonly CollectionBuilderConfiguration _config;
    private readonly FieldGenerator<TId> _fieldGenerator;
    private readonly LookupMethodGenerator _lookupMethodGenerator;
    private readonly EmptyClassGenerator _emptyClassGenerator;
    private readonly StaticConstructorGenerator<TId> _staticConstructorGenerator;
    private readonly ValuePropertyGenerator<TId> _valuePropertyGenerator;

    public GenericCollectionBuilder(CollectionBuilderConfiguration config)
    {
        _config = config ?? throw new ArgumentNullException(nameof(config));
        _config.Validate();

        // Initialize generators
        _fieldGenerator = new FieldGenerator<TId>(config);
        _lookupMethodGenerator = new LookupMethodGenerator(config);
        _emptyClassGenerator = new EmptyClassGenerator(config);
        _staticConstructorGenerator = new StaticConstructorGenerator<TId>(config);
        _valuePropertyGenerator = new ValuePropertyGenerator<TId>(config);
    }
    // ... fluent methods and Build()
}

Usage example (for int-based TypeCollections):

var config = CollectionBuilderConfiguration.ForTypeCollections();
var builder = new GenericCollectionBuilder<int>(config);
var director = new GenericCollectionDirector<int>(builder);

var generatedCode = director.ConstructFullCollection(
    definition: typeInfoModel,
    values: valueList,
    returnType: "IMyType",
    compilation: compilation);

Models

GenericTypeInfoModel

Metadata about the collection type being generated. This is a generic wrapper around CollectionTypeInfoModel<TId>.

From CollectionTypeInfoModel.cs:14-80:

public class CollectionTypeInfoModel<TId> : IInputInfoModel, IEquatable<CollectionTypeInfoModel<TId>>
    where TId : struct
{
    public string Namespace { get; set; } = string.Empty;
    public string ClassName { get; set; } = string.Empty;
    public string FullTypeName { get; set; } = string.Empty;
    public bool IsGenericType { get; set; }
    public string CollectionName { get; set; } = string.Empty;
    public string? CollectionBaseType { get; set; }
    public bool GenerateFactoryMethods { get; set; } = true;
    public bool GenerateStaticCollection { get; set; } = true;
    public bool Generic { get; set; }
    public string Strategy { get; set; } = "Default";
    public CollectionStrategy CollectionStrategy { get; set; } = CollectionStrategy.Immutable;
    public StringComparison NameComparison { get; set; } = StringComparison.OrdinalIgnoreCase;
    public bool IncludeReferencedAssemblies { get; set; }
    public bool UseMethods { get; set; }
    public bool UseDictionaryStorage { get; set; } = true;
    public string? KeyType { get; set; }
    public bool IsParentCollection { get; set; }
    public string? MemberOfParent { get; set; }
    public string? ReturnType { get; set; }
    public bool InheritsFromCollectionBase { get; set; }
    public EquatableArray<PropertyLookupInfoModel> LookupProperties { get; set; }
    public EquatableArray<CollectionValueInfoModel<TId>> ConcreteTypes { get; set; }
    // ... additional properties for generic type handling
}

GenericValueInfoModel

Metadata about individual collection values/options.

From CollectionValueInfoModel.cs:14-95:

public class CollectionValueInfoModel<TId> : IInputInfoModel, IEquatable<CollectionValueInfoModel<TId>>
    where TId : struct
{
    public string FullTypeName { get; set; } = string.Empty;
    public string ShortTypeName { get; set; } = string.Empty;
    public string Name { get; set; } = string.Empty;
    public bool Include { get; set; } = true;
    public int Order { get; set; }
    public string? Description { get; set; }
    public bool IsAbstract { get; set; }
    public bool IsStatic { get; set; }
    public bool IsGenericType { get; set; }
    public IDictionary<string, string> Properties { get; }
    public ISet<string> Categories { get; }
    public string? ReturnType { get; set; }
    public string? ReturnTypeNamespace { get; set; }
    public bool? GenerateFactoryMethod { get; set; }
    public IList<ConstructorInfo> Constructors { get; set; } = new List<ConstructorInfo>();
    public TId? BaseConstructorId { get; set; }
}

Non-generic aliases are provided for backward compatibility:

public class GenericTypeInfoModel : GenericTypeInfoModel<int> { }
public class GenericValueInfoModel : GenericValueInfoModel<int> { }

Generation Process

Step-by-Step Flow

  1. Configuration - Create CollectionBuilderConfiguration with collection-specific settings
  2. Model Creation - Build GenericTypeInfoModel and List<GenericValueInfoModel> from source analysis
  3. Director Setup - Create director with configured builder
  4. Generation - Call ConstructFullCollection or ConstructSimplifiedCollection
  5. Output - Receive complete C# source code as string

Internal Build Process

When Build() is called:

  1. Class Declaration - Creates partial class with proper modifiers
  2. Using Directives - Collects and deduplicates namespaces from all types
  3. Fields - Generates _all (FrozenDictionary), _empty, and lookup dictionaries
  4. Static Constructor - Initializes all static fields
  5. Lookup Methods - Generates ById(), ByName(), custom lookups
  6. Value Properties - Generates static properties for each value
  7. Helper Methods - Generates All(), NotFound() methods

Generated Code Structure

The builder generates a partial class with the following structure. Key generation logic is in GenericCollectionBuilder.Build().

From GenericCollectionBuilder.cs:127-347:

The generated code includes:

  1. Using directives - Collects namespaces from all value types and adds required system namespaces
  2. Static fields - _all (FrozenDictionary for Immutable strategy, ConcurrentDictionary for Mutable), _empty, and lookup dictionaries
  3. Static constructor - Initializes all fields via StaticConstructorGenerator
  4. Lookup methods - Generated via LookupMethodGenerator based on LookupProperties
  5. All() method - Returns _all.Values.ToList()
  6. NotFound() method - Returns _empty
  7. Register() method - For Mutable and Factory strategies only
  8. Value properties - Static properties for each discovered value via ValuePropertyGenerator

See the FractalDataWorks.Collections.SourceGenerators package for actual generated output examples.

Helper Services

GenericTypeHelper

Utilities for working with generic types in Roslyn. Handles type arity detection, metadata format conversion, and generic type parameter extraction.

From GenericTypeHelper.cs:13-200:

public static class GenericTypeHelper
{
    /// <summary>
    /// Checks if a type is generic (has type parameters).
    /// </summary>
    public static bool IsGenericType(INamedTypeSymbol? typeSymbol);

    /// <summary>
    /// Gets the arity (number of type parameters) for a type.
    /// Returns 0 for non-generic types.
    /// </summary>
    public static int GetArity(INamedTypeSymbol? typeSymbol);

    /// <summary>
    /// Gets the metadata name for a type (e.g., "Type`2" for Type<T1,T2>).
    /// This is the format required by Compilation.GetTypeByMetadataName().
    /// </summary>
    public static string GetMetadataName(INamedTypeSymbol typeSymbol);

    /// <summary>
    /// Gets the fully qualified metadata name including namespace (e.g., "Namespace.Type`2").
    /// </summary>
    public static string GetFullMetadataName(INamedTypeSymbol typeSymbol);

    /// <summary>
    /// Gets the type parameter list as a string (e.g., "<T1, T2, T3>").
    /// </summary>
    public static string GetTypeParameterList(INamedTypeSymbol typeSymbol);

    /// <summary>
    /// Gets the type parameter constraints for class declarations.
    /// </summary>
    public static string GetTypeParameterConstraints(INamedTypeSymbol typeSymbol, string indent = "    ");
}

AttributeBasedGeneratorHelper

Helper for creating ForAttributeWithMetadataName-based incremental generators with optimized attribute discovery.

From AttributeBasedGeneratorHelper.cs:16-138:

public static class AttributeBasedGeneratorHelper
{
    /// <summary>
    /// Creates an optimized provider for discovering types with a specific attribute.
    /// Uses ForAttributeWithMetadataName for faster discovery than manual scanning.
    /// </summary>
    public static IncrementalValuesProvider<(INamedTypeSymbol? TypeSymbol, AttributeData? Attribute)>
        CreateAttributeProvider(
            IncrementalGeneratorInitializationContext context,
            string attributeFullName,
            Func<SyntaxNode, CancellationToken, bool>? predicate = null);

    /// <summary>
    /// Filters option types that belong to a specific collection.
    /// </summary>
    public static IReadOnlyList<INamedTypeSymbol> FilterRelevantOptions(...);

    /// <summary>
    /// Checks if a collection should generate code (only in origin assembly).
    /// </summary>
    public static bool ShouldGenerateForCollection(...);

    /// <summary>
    /// Extracts a Type argument from an attribute's constructor at a specific index.
    /// </summary>
    public static INamedTypeSymbol? ExtractTypeArgument(AttributeData attribute, int index);
}

Performance Characteristics

  • Compile-Time Generation - All code generated during compilation (zero runtime reflection)
  • FrozenDictionary Lookups - O(1) lookup performance for Immutable strategy (uses System.Collections.Frozen.FrozenDictionary)
  • ConcurrentDictionary - Thread-safe O(1) lookups for Mutable strategy
  • Minimal Allocations - Static fields initialized once in static constructor
  • String Comparisons - Configurable via NameComparison property (default: OrdinalIgnoreCase)

Usage by Specialized Generators

This package serves as the foundation for:

  1. FractalDataWorks.Collections.SourceGenerators - TypeCollection AND ServiceType collection generation
  2. FractalDataWorks.Messages.SourceGenerators - Message collection generation

NOTE: There is NO separate ServiceTypes.SourceGenerators - the Collections generator handles both TypeCollections AND ServiceTypes.

Each specialized generator:

  • Creates appropriate CollectionBuilderConfiguration
  • Collects type metadata into GenericTypeInfoModel and GenericValueInfoModel instances
  • Uses GenericCollectionDirector to orchestrate generation
  • Handles domain-specific logic (validation, diagnostics, etc.)

Windows 260-Character Path Limit Workaround

The Problem

Source generators that reference multiple NuGet packages face a critical issue on Windows: the 260-character path limit. When Roslyn loads a source generator, it creates deep nested paths for each dependency:

C:\Users\username\.nuget\packages\
  microsoft.codeanalysis.csharp\5.0.0\lib\netstandard2.0\
    Microsoft.CodeAnalysis.CSharp.dll

With multiple dependencies, the generated paths exceed 260 characters, causing:

  • Silent failures - Generator loads but dependencies don't resolve
  • Runtime exceptions - FileNotFoundException when accessing dependency types
  • Inconsistent behavior - Works in some environments, fails in others

Dependency Approaches

FractalDataWorks source generators use two approaches depending on their needs:

Minimal Dependencies (Collections.SourceGenerators)

Collections.SourceGenerators uses only Roslyn APIs:

<ItemGroup>
  <PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
  <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all">
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>
</ItemGroup>
Aspect Benefit
Build time No post-build merge step
Debugging Direct source mapping works
Compatibility No version conflicts possible
Package size Smaller, only generator code
Path limits No deep dependency chains
ILRepack (Messages/MessageLogging.SourceGenerators)

Messages.SourceGenerators and MessageLogging.SourceGenerators use ILRepack because they depend on CodeBuilder libraries:

<ItemGroup>
  <PackageReference Include="ILRepack.Lib.MSBuild.Task" PrivateAssets="all" />
  <ProjectReference Include="..\FractalDataWorks.CodeBuilder.CSharp\..." PrivateAssets="all" />
</ItemGroup>

These merge dependencies post-build into a single DLL.

Dependencies

This package has the following dependencies:

From FractalDataWorks.SourceGenerators.csproj:

<ItemGroup>
  <PackageReference Include="Microsoft.CodeAnalysis.Analyzers">
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    <PrivateAssets>all</PrivateAssets>
  </PackageReference>
  <PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
  <PackageReference Include="Microsoft.Bcl.HashCode" />
</ItemGroup>

<ItemGroup>
  <ProjectReference Include="..\FractalDataWorks.CodeBuilder.Abstractions\..." />
  <ProjectReference Include="..\FractalDataWorks.CodeBuilder.CSharp\..." />
</ItemGroup>
  • Microsoft.CodeAnalysis.CSharp - Roslyn API for source generation
  • Microsoft.CodeAnalysis.Analyzers - Analyzer SDK
  • Microsoft.Bcl.HashCode - HashCode support for netstandard2.0
  • FractalDataWorks.CodeBuilder.Abstractions - Fluent code builder interfaces
  • FractalDataWorks.CodeBuilder.CSharp - C# code builder implementation
Generator Additional Dependencies Approach
Collections.SourceGenerators None beyond Roslyn Minimal dependencies
Messages.SourceGenerators CodeBuilder, Messages ILRepack merge
MessageLogging.SourceGenerators CodeBuilder, MessageLogging ILRepack merge

Integration Example

Example pattern for creating a specialized source generator using this infrastructure:

// In a specialized source generator
[Generator]
public class MyCollectionGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // Use AttributeBasedGeneratorHelper for optimized attribute discovery
        var collectionProvider = AttributeBasedGeneratorHelper.CreateAttributeProvider(
            context,
            "MyNamespace.MyCollectionAttribute");

        context.RegisterSourceOutput(collectionProvider, (spc, source) =>
        {
            // 1. Create configuration
            var config = CollectionBuilderConfiguration.ForTypeCollections();

            // 2. Build models from syntax analysis
            var definition = BuildDefinitionModel(source);  // Returns GenericTypeInfoModel<int>
            var values = CollectValues(source);             // Returns IList<GenericValueInfoModel<int>>

            // 3. Generate code using generic builder
            var builder = new GenericCollectionBuilder<int>(config);
            var director = new GenericCollectionDirector<int>(builder);
            var code = director.ConstructFullCollection(
                definition,
                values,
                "IMyInterface",
                spc.Compilation);

            // 4. Add to compilation
            spc.AddSource($"{definition.CollectionName}.g.cs", code);
        });
    }
}

For Guid-based ServiceTypes, use GenericCollectionBuilder<Guid> and GenericCollectionDirector<Guid>.

Design Principles

  1. Single Responsibility - Each generator handles one concern
  2. Open/Closed - Extensible through configuration, closed for modification
  3. Builder Pattern - Fluent API for complex object construction
  4. Director Pattern - Encapsulates construction logic and strategies
  5. Immutable Models - All models use init accessors
  6. Null Safety - Nullable reference types enabled

Testing Considerations

When testing generators using this infrastructure:

  1. Mock Compilation for type resolution
  2. Provide complete GenericTypeInfoModel and GenericValueInfoModel data
  3. Verify generated code compiles using Roslyn CSharpCompilation
  4. Test different CollectionGenerationMode scenarios
  5. Validate namespace collection and deduplication
  • FractalDataWorks.CodeBuilder.Abstractions - Fluent API for code generation
  • FractalDataWorks.CodeBuilder.CSharp - C# implementation of code builders
  • FractalDataWorks.Collections - Runtime TypeCollection infrastructure
  • FractalDataWorks.Services.Abstractions - Runtime ServiceType infrastructure
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.SourceGenerators:

Package Downloads
FractalDataWorks.Configuration.SourceGenerators

Development tools and utilities for the FractalDataWorks ecosystem. Build:

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
Loading failed