TypedStateBuilder.Generator 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package TypedStateBuilder.Generator --version 1.0.0
                    
NuGet\Install-Package TypedStateBuilder.Generator -Version 1.0.0
                    
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="TypedStateBuilder.Generator" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TypedStateBuilder.Generator" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="TypedStateBuilder.Generator" />
                    
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 TypedStateBuilder.Generator --version 1.0.0
                    
#r "nuget: TypedStateBuilder.Generator, 1.0.0"
                    
#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 TypedStateBuilder.Generator@1.0.0
                    
#: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=TypedStateBuilder.Generator&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=TypedStateBuilder.Generator&version=1.0.0
                    
Install as a Cake Tool

TypedStateBuilder

A Roslyn incremental source generator that produces compile-time safe builders using the type-state pattern.

It takes a builder template and generates the boilerplate needed for a strongly-typed, guided construction API where correctness is enforced by the compiler - not by runtime checks or conventions.


Table of Contents


Why this exists

Traditional builders in C# rely on:

  • runtime validation
  • defensive programming
  • developer discipline

This often leads to:

  • missing required values
  • duplicated or conflicting assignments
  • scattered validation logic
  • bugs discovered only at runtime

What this solves

TypedStateBuilder shifts structural correctness to compile time, while keeping flexibility:

  • Build() is only available when all required values are set
  • required steps can be executed in any order
  • each step can only be called once (in the typed API)
  • optional values can be defaulted automatically
  • validation is centralized and always executed

Result: invalid builder usage becomes unrepresentable code, without sacrificing fluent API flexibility.

Note: value correctness is still enforced at runtime via validation.


Comparison

Feature Simple Builder Interface Step Builder TypedStateBuilder
Compile-time safety
Ensures required steps
Prevents duplicate steps
Flexible ordering
Boilerplate required Low High Low
Runtime overhead Low Medium Low
Default values Manual Manual Built-in
Validation Manual Manual Built-in
IDE experience High Medium High

Example

Define a builder template:

[TypedStateBuilder]
public class UserBuilder
{
    private readonly IEmailService _emailService;

    public UserBuilder(IEmailService emailService)
    {
        _emailService = emailService;
    }

    [StepForValue]
    [ValidateValue(nameof(ValidateEmail))]
    private string _email;

    [StepForValue]
    private string _name;

    [StepForValue(nameof(DefaultAge))]
    private int _age;

    private int DefaultAge() => 18;

    private async Task ValidateEmail(string email)
    {
        if (!await _emailService.IsValidAsync(email))
            throw new InvalidOperationException("Invalid email");
    }

    [Build]
    public User Build()
        => new User(_name, _email, _age);
}

Usage:

var user = TypedStateBuilders
    .CreateUserBuilder(emailService)
    .SetName("Alice")                 // order is flexible
    .SetEmail("alice@example.com")
    .Build();                         // age defaults to 18

Invalid usage is caught at compile time:

var invalid = TypedStateBuilders
    .CreateUserBuilder(emailService)
    .SetName("Alice")
    .Build(); // ❌ compile-time error (email not set)

Key takeaway

You get:

  • compile-time guarantees (required steps must be set)
  • flexible ordering (no enforced sequence)
  • no duplicated steps (cannot call the same step twice)
  • support for dependency injection (usable in validation, build logic, or default providers)

What gets generated

For each [TypedStateBuilder] class, the generator produces:

  • Typed wrapper (TypedMyBuilder<...>)
  • Fluent step extension methods
  • Strongly-typed build methods
  • Factory methods (CreateMyBuilder)
  • Internal accessor layer (via UnsafeAccessor)

You define a builder template, and the generator produces the repetitive type-state boilerplate.


How it works

Each step is encoded as a type-state:

ValueUnset → ValueSet

The wrapper carries one state per step as generic parameters.

Example progression:

TypedBuilder<ValueUnset, ValueUnset, ValueUnset>
    → SetName  → TypedBuilder<ValueSet, ValueUnset, ValueUnset>
    → SetEmail → TypedBuilder<ValueSet, ValueSet, ValueUnset>

Build() becomes available only when all required states are ValueSet.


Attributes API

Attribute Target Purpose Parameters Rules
TypedStateBuilder Class Enables generation None Must be non-nested, non-partial, no inheritance, public/internal
StepForValue Field Defines a step Optional: nameof(provider) Field must be mutable instance field
Build Method Defines build entry None Must be instance method
ValidateValue Field Adds validation nameof(validator) Must match validator signature

Defining steps

[StepForValue]
private string _name;

Rules

  • must be a field
  • must be instance
  • must not be static
  • must not be readonly

Each step generates:

builder.SetName(value)

Important behavior

  • each step can be called only once in the typed API
  • enforced by the type system, not runtime checks
  • underlying fields remain mutable, but repeated calls are not expressible

Optional values and defaults

[StepForValue(nameof(DefaultAge))]
private int _age;

Behavior

  • default values are assigned during Create...
  • the step becomes optional
  • optional steps can be skipped before calling Build()

Important details

  • the step is still ValueUnset in the type-state system
  • you can still call the step explicitly afterward
  • optionality affects build availability, not initial state

Validation

[ValidateValue(nameof(ValidateName))]
private string _name;

Rules

  • must use nameof(...)
  • must be declared on the builder
  • must accept exactly one parameter (field type)
  • must return void or Task

Behavior

  • runs automatically before build
  • runs for all steps (including optional/unset)
  • exceptions are aggregated
throw new AggregateException(...)

Execution details

  • async validators are executed synchronously
  • internally, GetAwaiter().GetResult() is used
  • this introduces blocking behavior

Build methods

[Build]
public User Build() => ...

Features

  • multiple build methods supported
  • parameters preserved
  • generic methods supported
  • async return types supported

Available only when required steps are satisfied.


Constructors

Constructors are exposed via factory methods:

TypedStateBuilders.CreateUserBuilder(...)

Features

  • multiple constructors supported
  • parameters preserved (ref, out, defaults)
  • accessibility does not matter

Dependency Injection

Constructor parameters flow directly into the builder:

  • store them in non-step fields

  • use them in:

    • build logic
    • validation
    • default providers

Performance characteristics

  • incremental generator (fast IDE experience)
  • no reflection
  • minimal runtime overhead
  • wrapper allocation per step transition
  • direct access via UnsafeAccessor

Notes

  • validation may allocate when exceptions occur
  • async validation introduces blocking due to GetAwaiter().GetResult()

Constraints and limitations

Builder shape

  • must be a class
  • must be non-nested
  • must be non-partial
  • must not use inheritance

Steps

  • fields only (no properties)
  • must be mutable
  • names must not collide after normalization

Validators

  • only void or Task supported
  • executed synchronously

Behavior constraints

  • steps are single-assignment
  • optional steps are not marked as set automatically
  • validation runs for all steps

Why use it

Strong compile-time guarantees

  • cannot call Build() prematurely
  • cannot forget required steps
  • cannot call steps multiple times

Flexible API design

  • steps in any order
  • no rigid step chaining
  • supports generics and multiple build paths

Built-in capabilities

  • optional values with defaults
  • centralized validation
  • exception aggregation

Clean encapsulation

  • works with private members
  • no need to expose internals

Reduced boilerplate

  • no step interfaces
  • no manual validation wiring
  • no runtime guards

Summary

TypedStateBuilder generates a compile-time verified builder API that combines:

  • flexibility (steps in any order)
  • safety (required steps enforced by the compiler)
  • simplicity (no manual type-state boilerplate)

All while letting you define builders in plain, idiomatic C#.

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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 107 4/20/2026
1.1.0 99 4/19/2026
1.0.0 104 4/17/2026