From 3faced99e7bb9350846c04a3773718516a8ffc4d Mon Sep 17 00:00:00 2001 From: Andrei Ignat Date: Fri, 29 May 2026 06:52:52 +0300 Subject: [PATCH 1/3] first --- README.md | 30 +- later.md | 2 +- v2/.tours/TypedStateBuilder.Generator.tour | 42 + v2/Generator/all.csv | 1 + v2/RSCGExamplesData/GeneratorDataRec.json | 6 + .../description.json | 22 + .../TypedStateBuilder.Generator/nuget.txt | 1 + .../TypedStateBuilder.Generator/readme.txt | 488 +++++++++ .../src/Builder.slnx | 3 + .../src/Builder/Builder.csproj | 21 + .../src/Builder/Person.cs | 33 + .../src/Builder/Program.cs | 13 + .../src/Builder/globals.cs | 1 + .../TypedStateBuilder.Generator/video.json | 39 + .../docs/Authors/Georgiy_Petrov.md | 4 +- .../docs/Categories/Builder.md | 4 +- .../docs/Categories/_PrimitiveBuilder.mdx | 2 + .../TypedStateBuilder.Generator.md | 962 ++++++++++++++++++ .../docs/RSCG-Examples/index.md | 13 +- v2/rscg_examples_site/docs/about.md | 2 +- v2/rscg_examples_site/docs/indexRSCG.md | 5 +- .../src/components/HomepageFeatures/index.js | 2 +- .../static/exports/RSCG.json | 8 + 23 files changed, 1690 insertions(+), 14 deletions(-) create mode 100644 v2/.tours/TypedStateBuilder.Generator.tour create mode 100644 v2/rscg_examples/TypedStateBuilder.Generator/description.json create mode 100644 v2/rscg_examples/TypedStateBuilder.Generator/nuget.txt create mode 100644 v2/rscg_examples/TypedStateBuilder.Generator/readme.txt create mode 100644 v2/rscg_examples/TypedStateBuilder.Generator/src/Builder.slnx create mode 100644 v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/Builder.csproj create mode 100644 v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/Person.cs create mode 100644 v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/Program.cs create mode 100644 v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/globals.cs create mode 100644 v2/rscg_examples/TypedStateBuilder.Generator/video.json create mode 100644 v2/rscg_examples_site/docs/RSCG-Examples/TypedStateBuilder.Generator.md diff --git a/README.md b/README.md index 5e19e5d89..a4bcc1532 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# RSCG - 270 Examples of Roslyn Source Code Generators / 16 created by Microsoft / +# RSCG - 271 Examples of Roslyn Source Code Generators / 16 created by Microsoft / -The RSCG_Examples repository is a comprehensive documentation system that automatically processes and showcases 270 Roslyn Source Code Generator (RSCG) examples. The system transforms individual RSCG projects into structured documentation with code examples and cross-referenced content with a searchable website and code example exports. +The RSCG_Examples repository is a comprehensive documentation system that automatically processes and showcases 271 Roslyn Source Code Generator (RSCG) examples. The system transforms individual RSCG projects into structured documentation with code examples and cross-referenced content with a searchable website and code example exports. This system serves as both a learning resource for .NET developers interested in source generators and an automated pipeline for maintaining up-to-date documentation about the RSCG ecosystem -## Latest Update : 2026-05-15 => 15 May 2026 +## Latest Update : 2026-05-16 => 16 May 2026 If you want to see examples with code, please click ***[List V2](https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG)*** @@ -24,8 +24,30 @@ If you want to be notified each time I add a new RSCG example , please click htt ## Content -Those are the 270 Roslyn Source Code Generators that I have tested you can see and download source code example. +Those are the 271 Roslyn Source Code Generators that I have tested you can see and download source code example. ( including 16 from Microsoft ) +### 271. [TypedStateBuilder.Generator](https://ignatandrei.github.io/RSCG_Examples/v2/docs/TypedStateBuilder.Generator) , in the [Builder](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#builder) category + +Generated on : 2026-05-16 => 16 May 2026 + +
+ Expand + + + +Author: Georgiy Petrov + +A Roslyn source generator that produces **compile-time safe builders** using the *type-state pattern*. + +Nuget: [https://www.nuget.org/packages/TypedStateBuilder.Generator/](https://www.nuget.org/packages/TypedStateBuilder.Generator/) + + +Link: [https://ignatandrei.github.io/RSCG_Examples/v2/docs/TypedStateBuilder.Generator](https://ignatandrei.github.io/RSCG_Examples/v2/docs/TypedStateBuilder.Generator) + +Source: [https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator](https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator) + +
+ ### 270. [AssemblyMetadata.Generators](https://ignatandrei.github.io/RSCG_Examples/v2/docs/AssemblyMetadata.Generators) , in the [EnhancementProject](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementproject) category Generated on : 2026-05-15 => 15 May 2026 diff --git a/later.md b/later.md index a38a35e21..4e77a68db 100644 --- a/later.md +++ b/later.md @@ -1,6 +1,6 @@ # Just later -## Latest Update : 2026-05-15 => 15 May 2026 +## Latest Update : 2026-05-16 => 16 May 2026 diff --git a/v2/.tours/TypedStateBuilder.Generator.tour b/v2/.tours/TypedStateBuilder.Generator.tour new file mode 100644 index 000000000..f74f1e4b0 --- /dev/null +++ b/v2/.tours/TypedStateBuilder.Generator.tour @@ -0,0 +1,42 @@ + +{ + "$schema": "https://aka.ms/codetour-schema", + "title": "TypedStateBuilder.Generator", + "steps": + [ + { + "file": "rscg_examples/TypedStateBuilder.Generator/src/Builder/Builder.csproj", + "description": "First, we add Nuget [TypedStateBuilder.Generator](https://www.nuget.org/packages/TypedStateBuilder.Generator/) in csproj ", + "pattern": "TypedStateBuilder.Generator" + } + + ,{ + "file": "rscg_examples/TypedStateBuilder.Generator/src/Builder/Person.cs", + "description": "File Person.cs ", + "pattern": "this is the code" + } + + ,{ + "file": "rscg_examples/TypedStateBuilder.Generator/src/Builder/Program.cs", + "description": "File Program.cs \r\n>> dotnet run --project rscg_examples/TypedStateBuilder.Generator/src/Builder/Builder.csproj ", + "pattern": "this is the code" + } + + + ,{ + "file": "rscg_examples/TypedStateBuilder.Generator/src/Builder/obj/GX/TypedStateBuilder.Generator/TypedStateBuilder.Generator.TypedStateBuilderIncrementalGenerator/TypedStateBuilder.Attributes.g.cs", + "description": "Generated File 2 from 2 : TypedStateBuilder.Attributes.g.cs ", + "line": 1 + } + + ,{ + "file": "rscg_examples/TypedStateBuilder.Generator/src/Builder/obj/GX/TypedStateBuilder.Generator/TypedStateBuilder.Generator.TypedStateBuilderIncrementalGenerator/global__Builder_PersonBuilder_7DB9C28B.TypedStateBuilder.g.cs", + "description": "Generated File 1 from 2 : global__Builder_PersonBuilder_7DB9C28B.TypedStateBuilder.g.cs ", + "line": 1 + } + + ], + + "ref": "main" + +} \ No newline at end of file diff --git a/v2/Generator/all.csv b/v2/Generator/all.csv index 4910b0b0d..204fead0a 100644 --- a/v2/Generator/all.csv +++ b/v2/Generator/all.csv @@ -269,3 +269,4 @@ Nr,Key,Source,Category 268,GenerateDispose, https://github.com/ItaiTzur76/GenerateDispose,Disposer 269,LinkDotNet.Enumeration, https://github.com/linkdotnet/Enumeration,Enum 270,AssemblyMetadata.Generators, https://github.com/loresoft/AssemblyMetadata.Generators,EnhancementProject +271,TypedStateBuilder.Generator, https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator,Builder diff --git a/v2/RSCGExamplesData/GeneratorDataRec.json b/v2/RSCGExamplesData/GeneratorDataRec.json index 2ef016942..03302b860 100644 --- a/v2/RSCGExamplesData/GeneratorDataRec.json +++ b/v2/RSCGExamplesData/GeneratorDataRec.json @@ -1637,4 +1637,10 @@ "dtStart": "2026-05-15T00:00:00", "show": true }, + { + "ID":"TypedStateBuilder.Generator", + "Category": 4, + "dtStart": "2026-05-16T00:00:00", + "show": true + } ] \ No newline at end of file diff --git a/v2/rscg_examples/TypedStateBuilder.Generator/description.json b/v2/rscg_examples/TypedStateBuilder.Generator/description.json new file mode 100644 index 000000000..9884d846f --- /dev/null +++ b/v2/rscg_examples/TypedStateBuilder.Generator/description.json @@ -0,0 +1,22 @@ +{ + "generator":{ + "name":"TypedStateBuilder.Generator", + "nuget":[ + "https://www.nuget.org/packages/TypedStateBuilder.Generator/" + ], + "link":"https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator", + "author":"Georgiy Petrov", + "source":"https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator" + }, + "data":{ + "goodFor":["Generate strongly typed state builders for C# applications, improving code readability and maintainability."], + "csprojDemo":"Builder.csproj", + "csFiles":["Program.cs","Person.cs"], + "excludeDirectoryGenerated":[""], + "includeAdditionalFiles":[""] + }, + "links":{ + "blog":"", + "video":"" + } +} \ No newline at end of file diff --git a/v2/rscg_examples/TypedStateBuilder.Generator/nuget.txt b/v2/rscg_examples/TypedStateBuilder.Generator/nuget.txt new file mode 100644 index 000000000..43b2d70e6 --- /dev/null +++ b/v2/rscg_examples/TypedStateBuilder.Generator/nuget.txt @@ -0,0 +1 @@ +A Roslyn source generator that produces **compile-time safe builders** using the *type-state pattern*. \ No newline at end of file diff --git a/v2/rscg_examples/TypedStateBuilder.Generator/readme.txt b/v2/rscg_examples/TypedStateBuilder.Generator/readme.txt new file mode 100644 index 000000000..6d9d020a6 --- /dev/null +++ b/v2/rscg_examples/TypedStateBuilder.Generator/readme.txt @@ -0,0 +1,488 @@ +# TypedStateBuilder + +A Roslyn incremental source generator that makes **invalid builder usage impossible to compile**. + +You define a normal builder class. The generator produces a fluent API where **invalid construction flows are not expressible through the generated API**. + +[![NuGet](https://img.shields.io/nuget/v/TypedStateBuilder.Generator.svg?logo=nuget)](https://www.nuget.org/packages/TypedStateBuilder.Generator) + +--- + +## Why + +Traditional builders rely on: + +* runtime validation +* defensive checks +* developer discipline + +This allows invalid usage such as: + +* missing required values +* conflicting assignments +* calling `Build()` too early + +These issues are only detected at runtime. + +TypedStateBuilder moves **structural correctness into the type system**, so incorrect usage cannot be expressed in the first place. + +Unlike interface-based step builders, this approach: + +* requires **no manual interfaces** +* avoids **state explosion** +* keeps your builder **simple and idiomatic** + +You write the builder once. The generator handles the rest. + +--- + +## What this solves + +TypedStateBuilder enforces correct builder usage while keeping the flexibility of a fluent API: + +* `Build()` is only available when required values are set +* required steps can be executed in any order (unless constrained by branching) +* each step can be applied only once (in the typed API) +* optional values can be defaulted automatically +* validation is centralized and automatically executed for applicable steps +* one logical step can expose multiple input shapes via overloads +* multiple branch-specific build paths can coexist safely + +Result: **invalid builder usage becomes unrepresentable code**, instead of something you have to guard against at runtime. + +> Structural correctness is enforced at compile time. +> Value correctness is still enforced at runtime via validation. + +--- + +## Comparison + +| Feature | Simple Builder | Interface Step Builder | TypedStateBuilder | +| ----------------------- | -------------- | ---------------------- | ----------------- | +| Compile-time safety | ❌ | ✅ | ✅ | +| Required steps enforced | ❌ | ✅ | ✅ | +| Prevent duplicate steps | ❌ | ✅ | ✅ | +| Flexible ordering | ✅ | ❌ | ✅ | +| Boilerplate | Low | High | Low | +| Default values | Manual | Manual | Built-in | +| Validation | Manual | Manual | Built-in | +| Step overloads | Manual | Manual | Built-in | +| Branch-specific builds | Manual | Manual | Built-in | + +--- + +## Example + +### Builder template + +```csharp +[TypedStateBuilder] +public class UserBuilder +{ + private readonly IEmailService _emailService; + + public UserBuilder(IEmailService emailService) + { + _emailService = emailService; + } + + [StepForValue] + [ValidateValue(nameof(ValidateEmail))] + private string _email; + + [StepForValue] + [StepOverload(nameof(FullNameToName))] + private string _name; + + [StepForValue(nameof(DefaultAge))] + private int _age; + + private int DefaultAge() => 18; + + private string FullNameToName(string firstName, string lastName) + => $"{firstName} {lastName}"; + + 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 + +```csharp +var user = TypedStateBuilders + .CreateUserBuilder(emailService) + .SetName("Alice", "Walker") + .SetEmail("alice@example.com") + .Build(); +``` + +The direct step method still exists: + +```csharp +.SetName("Alice Walker") +``` + +Invalid usage is caught at compile time: + +```csharp +var invalid = TypedStateBuilders + .CreateUserBuilder(emailService) + .SetName("Alice") + .Build(); // ❌ email not set +``` + +--- + +## What you write vs what you get + +You only write: + +* a normal class +* fields marked with `[StepForValue]` +* optional: + + * defaults (`[StepForValue(nameof(...))]`) + * validators (`[ValidateValue]`) + * overloads (`[StepOverload]`) + * branches (`[StepBranch]`) +* one or more `[Build]` methods + +The generator produces: + +* a typed wrapper (`TypedMyBuilder<...>`) +* fluent step methods (`SetX(...)`) +* compile-time enforcement of required steps +* build methods that are only available when valid + +No interfaces, no manual state tracking, no boilerplate. + +--- + +## How it works + +Each step is encoded as a **type-state transition**: + +```text +ValueUnset → ValueSet +``` + +The generated wrapper carries one state per step: + +```csharp +TypedBuilder + → SetName → TypedBuilder + → SetEmail → TypedBuilder +``` + +A build method becomes available only when all required states for that build path are `ValueSet`. + +### Step semantics + +Each step: + +* can be called exactly once (in the typed API) +* transitions its state from `ValueUnset` to `ValueSet` +* is enforced by the type system — not runtime checks + +The underlying builder remains mutable, but repeated assignments are not expressible through the generated API. + +--- + +## Branching + +### Mental model + +Think of branches like paths: + +``` +car/ +car/electric/ +bike/ +``` + +* `car` applies to `car/electric` +* `bike` is completely separate +* deeper paths build on their parents + +This keeps related steps together while preventing invalid combinations. + +--- + +### Example + +```csharp +[TypedStateBuilder] +public class VehicleBuilder +{ + [StepForValue] + private string _name; + + [StepForValue] + [StepBranch("car")] + private int _doorCount; + + [StepForValue(nameof(DefaultBatteryKWh))] + [StepBranch("car/electric")] + private int _batteryKWh; + + [StepForValue] + [StepBranch("bike")] + private bool _hasBell; + + private int DefaultBatteryKWh() => 75; + + [Build("car")] + public Vehicle BuildCar() + => Vehicle.Car(_name, _doorCount); + + [Build("car/electric")] + public Vehicle BuildElectricCar() + => Vehicle.ElectricCar(_name, _doorCount, _batteryKWh); + + [Build("bike")] + public Vehicle BuildBike() + => Vehicle.Bike(_name, _hasBell); +} +``` + +--- + +### Branch semantics + +#### Build requirement + +If any step uses branching, **all build methods must specify an explicit branch target**: + +```csharp +[Build("car")] +public Vehicle BuildCar() +``` + +Unbranched `[Build]` methods are not allowed in branched builders. + +--- + +#### Step applicability + +A step applies if: + +* it is unbranched, or +* its branch matches the build target, or +* its branch is a parent of the build target + +--- + +#### Step compatibility + +Two steps are compatible if: + +* either is unbranched, or +* one branch is the same as or a parent of the other + +Sibling branches are incompatible. + +--- + +#### Ancestor requirement + +If a step belongs to a deeper branch, any declared ancestor steps must already be set before it is callable. + +--- + +## Step overloads + +```csharp +[StepForValue] +[StepOverload(nameof(CreateName))] +private string _name; + +private string CreateName(string first, string last) + => $"{first} {last}"; +``` + +Generated API: + +```csharp +builder.SetName("Alice Walker"); +builder.SetName("Alice", "Walker"); +``` + +--- + +## Optional values and defaults + +```csharp +[StepForValue(nameof(DefaultAge))] +private int _age; +``` + +### Behavior + +* step becomes optional +* if unset, default runs during build +* state remains `ValueUnset` until build + +Defaults are applied before validation and build execution. + +--- + +## Validation + +```csharp +[ValidateValue(nameof(ValidateName))] +private string _name; +``` + +### Behavior + +* runs automatically before build +* runs only for steps applicable to the selected build path +* exceptions are aggregated: + +```csharp +throw new AggregateException(...) +``` + +### Execution details + +* defaults are applied first +* validators run next +* async validators execute synchronously (`GetAwaiter().GetResult()`) + +Note: + +* async validators are supported but executed synchronously +* there is currently no async build pipeline + +--- + +## Build methods + +```csharp +[Build] +public User Build() +``` + +or: + +```csharp +[Build("car")] +public Vehicle BuildCar() +``` + +### Behavior + +A build method: + +* is only callable when required steps are satisfied +* preserves parameters and generics +* runs defaults and validation before execution + +--- + +## What gets generated + +For each builder: + +* typed wrapper (`TypedMyBuilder<...>`) +* step extension methods +* step overload extension methods +* build extension methods +* factory methods (`CreateMyBuilder(...)`) +* internal accessor layer (`UnsafeAccessor`) + +--- + +## Constructors + +Constructors are exposed via: + +```csharp +TypedStateBuilders.CreateMyBuilder(...) +``` + +* parameters preserved +* defaults preserved +* initial state: all steps `ValueUnset` + +--- + +## Dependency Injection + +Constructor dependencies can be used in: + +* build logic +* validation +* default providers +* step overload methods + +--- + +## Performance + +* incremental generator (fast IDE experience) +* no reflection +* no runtime state tracking objects +* direct field/method access via generated accessors +* minimal runtime overhead +* wrapper allocation per step +* shared underlying builder instance + +Notes: + +* async validation blocks +* allocations mainly occur on validation failure + +--- + +## Constraints and limitations + +### Builder + +* class only +* non-nested +* non-partial +* no inheritance +* public or internal + +### Steps + +* fields only +* must be mutable +* no static or readonly + +### Branching + +* path-based, prefix matching +* explicit build targets required when used + +### Step overloads + +* must be non-generic +* must return field type +* must not collide + +### Validation + +* only `void` or `Task` supported + +--- + +## Summary + +TypedStateBuilder generates a builder API where: + +* required steps are enforced at compile time +* invalid construction paths cannot be expressed +* ordering remains flexible where valid +* branching enables multiple safe build paths + +You define a builder. The generator makes its correct usage explicit. diff --git a/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder.slnx b/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder.slnx new file mode 100644 index 000000000..507475890 --- /dev/null +++ b/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder.slnx @@ -0,0 +1,3 @@ + + + diff --git a/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/Builder.csproj b/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/Builder.csproj new file mode 100644 index 000000000..723e2d1a6 --- /dev/null +++ b/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/Builder.csproj @@ -0,0 +1,21 @@ + + + + Exe + net10.0 + enable + + + + true + $(BaseIntermediateOutputPath)\GX + + + + + + + + + + diff --git a/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/Person.cs b/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/Person.cs new file mode 100644 index 000000000..42e331626 --- /dev/null +++ b/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/Person.cs @@ -0,0 +1,33 @@ +using TypedStateBuilder; +namespace Builder; + +[TypedStateBuilder] +public class PersonBuilder +{ + + [StepForValue] + [ValidateValue(nameof(ValidateName))] + private string lastName = string.Empty; + + [StepForValue] + [ValidateValue(nameof(ValidateName))] + private string firstName = string.Empty; + + public void ValidateName(string name) + { + if (string.IsNullOrWhiteSpace(name) || name.Length <= 1) + { + throw new ArgumentException("Name must be at least 2 characters long.", nameof(name)); + } + } + + + [Build] + public Person Build() + => new Person(firstName, lastName); +} + +public record Person(string firstName, string lastName) +{ + public string FullName() => $"{firstName} {lastName}"; +} \ No newline at end of file diff --git a/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/Program.cs b/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/Program.cs new file mode 100644 index 000000000..d2cd251e4 --- /dev/null +++ b/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/Program.cs @@ -0,0 +1,13 @@ +using Builder; +using TypedStateBuilder; +Console.WriteLine("create person builder"); + +var p = TypedStateBuilders + .CreatePersonBuilder() + .SetFirstName("Andrei") + .SetLastName("Ignat") + .Build() + ; +; + +Console.WriteLine(p.FullName()); diff --git a/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/globals.cs b/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/globals.cs new file mode 100644 index 000000000..76d1fbe88 --- /dev/null +++ b/v2/rscg_examples/TypedStateBuilder.Generator/src/Builder/globals.cs @@ -0,0 +1 @@ +global using System; \ No newline at end of file diff --git a/v2/rscg_examples/TypedStateBuilder.Generator/video.json b/v2/rscg_examples/TypedStateBuilder.Generator/video.json new file mode 100644 index 000000000..c283f3bf1 --- /dev/null +++ b/v2/rscg_examples/TypedStateBuilder.Generator/video.json @@ -0,0 +1,39 @@ +{ + "scriptName": "TypedStateBuilder.Generator", + "steps": +[ + {"typeStep":"exec","arg":"clipchamp.exe launch"}, + {"typeStep":"text","arg": "Welcome to Roslyn Examples"}, + {"typeStep":"text","arg":"If you want to see more examples , see List Of RSCG"}, + {"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG"}, + {"typeStep":"text","arg": "My name is Andrei Ignat and I am deeply fond of Roslyn Source Code Generator. "}, + +{"typeStep":"text","arg": "Today I will present TypedStateBuilder.Generator . Generate strongly typed state builders for C# applications, improving code readability and maintainability. ."}, +{"typeStep":"browser","arg":"https://www.nuget.org/packages/TypedStateBuilder.Generator/"}, +{"typeStep":"text","arg": "The whole example is here"}, +{"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/TypedStateBuilder.Generator"}, +{"typeStep":"text","arg": "You can download the code from here"}, +{"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/TypedStateBuilder.Generator#download-example-net--c-"}, +{"typeStep":"text","arg":"Here is the code downloaded "}, +{"typeStep":"exec","arg":"explorer.exe /select,D:\\gth\\RSCG_Examples\\v2\\Generator.sln"}, +{"typeStep":"text","arg": "So , let's start the project with Visual Studio Code "}, +{"typeStep":"stepvscode","arg": "-n D:\\gth\\RSCG_Examples\\v2"}, + +{"typeStep":"text","arg": "To use it ,you will put the Nuget TypedStateBuilder.Generator into the csproj "}, + +{"typeStep":"stepvscode","arg": "-r -g D:\\gth\\RSCG_Examples\\v2\\rscg_examples\\TypedStateBuilder.Generator\\src\\Builder\\Builder.csproj"}, + +{"typeStep":"text","arg": "And now I will show you an example of using TypedStateBuilder.Generator"}, + +{"typeStep":"hide","arg": "now execute the tour in VSCode"}, +{"typeStep":"tour", "arg": "src/.tours/"}, +{"typeStep":"text","arg":" And I will execute the project"}, +{"typeStep":"showproj", "arg":"Builder.csproj"}, +{"typeStep":"text","arg":" This concludes the project"}, +{"typeStep":"waitseconds","arg":"30"}, +{"typeStep":"text","arg": "Remember, you can download the code from here"}, +{"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/TypedStateBuilder.Generator#download-example-net--c-", +SpeakTest=" "}, +{"typeStep":"waitseconds","arg":"30"}, +] +} diff --git a/v2/rscg_examples_site/docs/Authors/Georgiy_Petrov.md b/v2/rscg_examples_site/docs/Authors/Georgiy_Petrov.md index 5f4a45290..f453906c1 100644 --- a/v2/rscg_examples_site/docs/Authors/Georgiy_Petrov.md +++ b/v2/rscg_examples_site/docs/Authors/Georgiy_Petrov.md @@ -1,9 +1,11 @@ # Author : Georgiy Petrov -Number RSCG: 2 +Number RSCG: 3 1 [StepwiseBuilderGenerator](/docs/StepwiseBuilderGenerator) [![Nuget](https://img.shields.io/nuget/dt/StepwiseBuilderGenerator?label=StepwiseBuilderGenerator)](https://www.nuget.org/packages/StepwiseBuilderGenerator/) ![GitHub Repo stars](https://img.shields.io/github/stars/Georgiy-Petrov/StepwiseBuilderGenerator?style=social) 2025-03-23 2 [OrderedBuildersGenerator](/docs/OrderedBuildersGenerator) [![Nuget](https://img.shields.io/nuget/dt/OrderedBuildersGenerator?label=OrderedBuildersGenerator)](https://www.nuget.org/packages/OrderedBuildersGenerator/) ![GitHub Repo stars](https://img.shields.io/github/stars/Georgiy-Petrov/OrderedBuildersGenerator?style=social) 2025-12-18 + 3 [TypedStateBuilder.Generator](/docs/TypedStateBuilder.Generator) [![Nuget](https://img.shields.io/nuget/dt/TypedStateBuilder.Generator?label=TypedStateBuilder.Generator)](https://www.nuget.org/packages/TypedStateBuilder.Generator/) ![GitHub Repo stars](https://img.shields.io/github/stars/Georgiy-Petrov/TypedStateBuilder.Generator?style=social) 2026-05-16 + diff --git a/v2/rscg_examples_site/docs/Categories/Builder.md b/v2/rscg_examples_site/docs/Categories/Builder.md index 386043b92..f8e729e3b 100644 --- a/v2/rscg_examples_site/docs/Categories/Builder.md +++ b/v2/rscg_examples_site/docs/Categories/Builder.md @@ -1,6 +1,6 @@

Builder

-Number RSCG: 8 +Number RSCG: 9 1 [Architect.DomainModeling](/docs/Architect.DomainModeling) [![Nuget](https://img.shields.io/nuget/dt/Architect.DomainModeling?label=Architect.DomainModeling)](https://www.nuget.org/packages/Architect.DomainModeling/) ![GitHub Repo stars](https://img.shields.io/github/stars/TheArchitectDev/Architect.DomainModeling?style=social) 2024-03-02 @@ -17,4 +17,6 @@ Number RSCG: 8 7 [ShadowWriterBuilder](/docs/ShadowWriterBuilder) [![Nuget](https://img.shields.io/nuget/dt/ShadowWriter?label=ShadowWriter)](https://www.nuget.org/packages/ShadowWriter/) ![GitHub Repo stars](https://img.shields.io/github/stars/StefanStolz/ShadowWriter?style=social) 2025-07-24 8 [StepwiseBuilderGenerator](/docs/StepwiseBuilderGenerator) [![Nuget](https://img.shields.io/nuget/dt/StepwiseBuilderGenerator?label=StepwiseBuilderGenerator)](https://www.nuget.org/packages/StepwiseBuilderGenerator/) ![GitHub Repo stars](https://img.shields.io/github/stars/Georgiy-Petrov/StepwiseBuilderGenerator?style=social) 2025-03-23 + + 9 [TypedStateBuilder.Generator](/docs/TypedStateBuilder.Generator) [![Nuget](https://img.shields.io/nuget/dt/TypedStateBuilder.Generator?label=TypedStateBuilder.Generator)](https://www.nuget.org/packages/TypedStateBuilder.Generator/) ![GitHub Repo stars](https://img.shields.io/github/stars/Georgiy-Petrov/TypedStateBuilder.Generator?style=social) 2026-05-16 \ No newline at end of file diff --git a/v2/rscg_examples_site/docs/Categories/_PrimitiveBuilder.mdx b/v2/rscg_examples_site/docs/Categories/_PrimitiveBuilder.mdx index 0a8e4046c..5dc13f247 100644 --- a/v2/rscg_examples_site/docs/Categories/_PrimitiveBuilder.mdx +++ b/v2/rscg_examples_site/docs/Categories/_PrimitiveBuilder.mdx @@ -16,6 +16,8 @@ 8 [StepwiseBuilderGenerator](/docs/StepwiseBuilderGenerator) [![Nuget](https://img.shields.io/nuget/dt/StepwiseBuilderGenerator?label=StepwiseBuilderGenerator)](https://www.nuget.org/packages/StepwiseBuilderGenerator/) ![GitHub Repo stars](https://img.shields.io/github/stars/Georgiy-Petrov/StepwiseBuilderGenerator?style=social) 2025-03-23 + 9 [TypedStateBuilder.Generator](/docs/TypedStateBuilder.Generator) [![Nuget](https://img.shields.io/nuget/dt/TypedStateBuilder.Generator?label=TypedStateBuilder.Generator)](https://www.nuget.org/packages/TypedStateBuilder.Generator/) ![GitHub Repo stars](https://img.shields.io/github/stars/Georgiy-Petrov/TypedStateBuilder.Generator?style=social) 2026-05-16 + ### See category [Builder](/docs/Categories/Builder) diff --git a/v2/rscg_examples_site/docs/RSCG-Examples/TypedStateBuilder.Generator.md b/v2/rscg_examples_site/docs/RSCG-Examples/TypedStateBuilder.Generator.md new file mode 100644 index 000000000..45a5d07e8 --- /dev/null +++ b/v2/rscg_examples_site/docs/RSCG-Examples/TypedStateBuilder.Generator.md @@ -0,0 +1,962 @@ +--- +sidebar_position: 2710 +title: 271 - TypedStateBuilder.Generator +description: Generate strongly typed state builders for C# applications, improving code readability and maintainability. +slug: /TypedStateBuilder.Generator +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import TOCInline from '@theme/TOCInline'; +import SameCategory from '../Categories/_PrimitiveBuilder.mdx'; + +# TypedStateBuilder.Generator by Georgiy Petrov + + + + +## NuGet / site data +[![Nuget](https://img.shields.io/nuget/dt/TypedStateBuilder.Generator?label=TypedStateBuilder.Generator)](https://www.nuget.org/packages/TypedStateBuilder.Generator/) +[![GitHub last commit](https://img.shields.io/github/last-commit/Georgiy-Petrov/TypedStateBuilder.Generator?label=updated)](https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator) +![GitHub Repo stars](https://img.shields.io/github/stars/Georgiy-Petrov/TypedStateBuilder.Generator?style=social) + +## Details + +### Info +:::info + +Name: **TypedStateBuilder.Generator** + +A Roslyn source generator that produces **compile-time safe builders** using the *type-state pattern*. + +Author: Georgiy Petrov + +NuGet: +*https://www.nuget.org/packages/TypedStateBuilder.Generator/* + + +You can find more details at https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator + +Source: https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator + +::: + +### Author +:::note +Georgiy Petrov +![Alt text](https://github.com/Georgiy-Petrov.png) +::: + +## Original Readme +:::note + +# TypedStateBuilder + +A Roslyn incremental source generator that makes **invalid builder usage impossible to compile**. + +You define a normal builder class. The generator produces a fluent API where **invalid construction flows are not expressible through the generated API**. + +[![NuGet](https://img.shields.io/nuget/v/TypedStateBuilder.Generator.svg?logo=nuget)](https://www.nuget.org/packages/TypedStateBuilder.Generator) + +--- + +## Why + +Traditional builders rely on: + +* runtime validation +* defensive checks +* developer discipline + +This allows invalid usage such as: + +* missing required values +* conflicting assignments +* calling `Build()` too early + +These issues are only detected at runtime. + +TypedStateBuilder moves **structural correctness into the type system**, so incorrect usage cannot be expressed in the first place. + +Unlike interface-based step builders, this approach: + +* requires **no manual interfaces** +* avoids **state explosion** +* keeps your builder **simple and idiomatic** + +You write the builder once. The generator handles the rest. + +--- + +## What this solves + +TypedStateBuilder enforces correct builder usage while keeping the flexibility of a fluent API: + +* `Build()` is only available when required values are set +* required steps can be executed in any order (unless constrained by branching) +* each step can be applied only once (in the typed API) +* optional values can be defaulted automatically +* validation is centralized and automatically executed for applicable steps +* one logical step can expose multiple input shapes via overloads +* multiple branch-specific build paths can coexist safely + +Result: **invalid builder usage becomes unrepresentable code**, instead of something you have to guard against at runtime. + +> Structural correctness is enforced at compile time. +> Value correctness is still enforced at runtime via validation. + +--- + +## Comparison + +| Feature | Simple Builder | Interface Step Builder | TypedStateBuilder | +| ----------------------- | -------------- | ---------------------- | ----------------- | +| Compile-time safety | ❌ | ✅ | ✅ | +| Required steps enforced | ❌ | ✅ | ✅ | +| Prevent duplicate steps | ❌ | ✅ | ✅ | +| Flexible ordering | ✅ | ❌ | ✅ | +| Boilerplate | Low | High | Low | +| Default values | Manual | Manual | Built-in | +| Validation | Manual | Manual | Built-in | +| Step overloads | Manual | Manual | Built-in | +| Branch-specific builds | Manual | Manual | Built-in | + +--- + +## Example + +### Builder template + +```csharp +[TypedStateBuilder] +public class UserBuilder +{ + private readonly IEmailService _emailService; + + public UserBuilder(IEmailService emailService) + { + _emailService = emailService; + } + + [StepForValue] + [ValidateValue(nameof(ValidateEmail))] + private string _email; + + [StepForValue] + [StepOverload(nameof(FullNameToName))] + private string _name; + + [StepForValue(nameof(DefaultAge))] + private int _age; + + private int DefaultAge() => 18; + + private string FullNameToName(string firstName, string lastName) + => $"{firstName} {lastName}"; + + 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 + +```csharp +var user = TypedStateBuilders + .CreateUserBuilder(emailService) + .SetName("Alice", "Walker") + .SetEmail("alice@example.com") + .Build(); +``` + +The direct step method still exists: + +```csharp +.SetName("Alice Walker") +``` + +Invalid usage is caught at compile time: + +```csharp +var invalid = TypedStateBuilders + .CreateUserBuilder(emailService) + .SetName("Alice") + .Build(); // ❌ email not set +``` + +--- + +## What you write vs what you get + +You only write: + +* a normal class +* fields marked with `[StepForValue]` +* optional: + + * defaults (`[StepForValue(nameof(...))]`) + * validators (`[ValidateValue]`) + * overloads (`[StepOverload]`) + * branches (`[StepBranch]`) +* one or more `[Build]` methods + +The generator produces: + +* a typed wrapper (`TypedMyBuilder<...>`) +* fluent step methods (`SetX(...)`) +* compile-time enforcement of required steps +* build methods that are only available when valid + +No interfaces, no manual state tracking, no boilerplate. + +--- + +## How it works + +Each step is encoded as a **type-state transition**: + +```text +ValueUnset → ValueSet +``` + +The generated wrapper carries one state per step: + +```csharp +TypedBuilder + → SetName → TypedBuilder + → SetEmail → TypedBuilder +``` + +A build method becomes available only when all required states for that build path are `ValueSet`. + +### Step semantics + +Each step: + +* can be called exactly once (in the typed API) +* transitions its state from `ValueUnset` to `ValueSet` +* is enforced by the type system — not runtime checks + +The underlying builder remains mutable, but repeated assignments are not expressible through the generated API. + +--- + +## Branching + +### Mental model + +Think of branches like paths: + +``` +car/ +car/electric/ +bike/ +``` + +* `car` applies to `car/electric` +* `bike` is completely separate +* deeper paths build on their parents + +This keeps related steps together while preventing invalid combinations. + +--- + +### Example + +```csharp +[TypedStateBuilder] +public class VehicleBuilder +{ + [StepForValue] + private string _name; + + [StepForValue] + [StepBranch("car")] + private int _doorCount; + + [StepForValue(nameof(DefaultBatteryKWh))] + [StepBranch("car/electric")] + private int _batteryKWh; + + [StepForValue] + [StepBranch("bike")] + private bool _hasBell; + + private int DefaultBatteryKWh() => 75; + + [Build("car")] + public Vehicle BuildCar() + => Vehicle.Car(_name, _doorCount); + + [Build("car/electric")] + public Vehicle BuildElectricCar() + => Vehicle.ElectricCar(_name, _doorCount, _batteryKWh); + + [Build("bike")] + public Vehicle BuildBike() + => Vehicle.Bike(_name, _hasBell); +} +``` + +--- + +### Branch semantics + +#### Build requirement + +If any step uses branching, **all build methods must specify an explicit branch target**: + +```csharp +[Build("car")] +public Vehicle BuildCar() +``` + +Unbranched `[Build]` methods are not allowed in branched builders. + +--- + +#### Step applicability + +A step applies if: + +* it is unbranched, or +* its branch matches the build target, or +* its branch is a parent of the build target + +--- + +#### Step compatibility + +Two steps are compatible if: + +* either is unbranched, or +* one branch is the same as or a parent of the other + +Sibling branches are incompatible. + +--- + +#### Ancestor requirement + +If a step belongs to a deeper branch, any declared ancestor steps must already be set before it is callable. + +--- + +## Step overloads + +```csharp +[StepForValue] +[StepOverload(nameof(CreateName))] +private string _name; + +private string CreateName(string first, string last) + => $"{first} {last}"; +``` + +Generated API: + +```csharp +builder.SetName("Alice Walker"); +builder.SetName("Alice", "Walker"); +``` + +--- + +## Optional values and defaults + +```csharp +[StepForValue(nameof(DefaultAge))] +private int _age; +``` + +### Behavior + +* step becomes optional +* if unset, default runs during build +* state remains `ValueUnset` until build + +Defaults are applied before validation and build execution. + +--- + +## Validation + +```csharp +[ValidateValue(nameof(ValidateName))] +private string _name; +``` + +### Behavior + +* runs automatically before build +* runs only for steps applicable to the selected build path +* exceptions are aggregated: + +```csharp +throw new AggregateException(...) +``` + +### Execution details + +* defaults are applied first +* validators run next +* async validators execute synchronously (`GetAwaiter().GetResult()`) + +Note: + +* async validators are supported but executed synchronously +* there is currently no async build pipeline + +--- + +## Build methods + +```csharp +[Build] +public User Build() +``` + +or: + +```csharp +[Build("car")] +public Vehicle BuildCar() +``` + +### Behavior + +A build method: + +* is only callable when required steps are satisfied +* preserves parameters and generics +* runs defaults and validation before execution + +--- + +## What gets generated + +For each builder: + +* typed wrapper (`TypedMyBuilder<...>`) +* step extension methods +* step overload extension methods +* build extension methods +* factory methods (`CreateMyBuilder(...)`) +* internal accessor layer (`UnsafeAccessor`) + +--- + +## Constructors + +Constructors are exposed via: + +```csharp +TypedStateBuilders.CreateMyBuilder(...) +``` + +* parameters preserved +* defaults preserved +* initial state: all steps `ValueUnset` + +--- + +## Dependency Injection + +Constructor dependencies can be used in: + +* build logic +* validation +* default providers +* step overload methods + +--- + +## Performance + +* incremental generator (fast IDE experience) +* no reflection +* no runtime state tracking objects +* direct field/method access via generated accessors +* minimal runtime overhead +* wrapper allocation per step +* shared underlying builder instance + +Notes: + +* async validation blocks +* allocations mainly occur on validation failure + +--- + +## Constraints and limitations + +### Builder + +* class only +* non-nested +* non-partial +* no inheritance +* public or internal + +### Steps + +* fields only +* must be mutable +* no static or readonly + +### Branching + +* path-based, prefix matching +* explicit build targets required when used + +### Step overloads + +* must be non-generic +* must return field type +* must not collide + +### Validation + +* only `void` or `Task` supported + +--- + +## Summary + +TypedStateBuilder generates a builder API where: + +* required steps are enforced at compile time +* invalid construction paths cannot be expressed +* ordering remains flexible where valid +* branching enables multiple safe build paths + +You define a builder. The generator makes its correct usage explicit. + + +::: + +### About +:::note + +Generate strongly typed state builders for C# applications, improving code readability and maintainability. + + +::: + +## How to use + +### Example (source csproj, source files) + + + + + +This is the CSharp Project that references **TypedStateBuilder.Generator** +```xml showLineNumbers {15} + + + + Exe + net10.0 + enable + + + + true + $(BaseIntermediateOutputPath)\GX + + + + + + + + + + + +``` + + + + + + This is the use of **TypedStateBuilder.Generator** in *Program.cs* + +```csharp showLineNumbers +using Builder; +using TypedStateBuilder; +Console.WriteLine("create person builder"); + +var p = TypedStateBuilders + .CreatePersonBuilder() + .SetFirstName("Andrei") + .SetLastName("Ignat") + .Build() + ; +; + +Console.WriteLine(p.FullName()); + +``` + + + + + This is the use of **TypedStateBuilder.Generator** in *Person.cs* + +```csharp showLineNumbers +using TypedStateBuilder; +namespace Builder; + +[TypedStateBuilder] +public class PersonBuilder +{ + + [StepForValue] + [ValidateValue(nameof(ValidateName))] + private string lastName = string.Empty; + + [StepForValue] + [ValidateValue(nameof(ValidateName))] + private string firstName = string.Empty; + + public void ValidateName(string name) + { + if (string.IsNullOrWhiteSpace(name) || name.Length <= 1) + { + throw new ArgumentException("Name must be at least 2 characters long.", nameof(name)); + } + } + + + [Build] + public Person Build() + => new Person(firstName, lastName); +} + +public record Person(string firstName, string lastName) +{ + public string FullName() => $"{firstName} {lastName}"; +} +``` + + + + +### Generated Files + +Those are taken from $(BaseIntermediateOutputPath)\GX + + + + +```csharp showLineNumbers +// +#nullable enable +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Builder +{ + +file static class PersonBuilder_Accessors +{ + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + internal static extern global::Builder.PersonBuilder Create(); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "lastName")] + internal static extern ref string SetLastNameField(global::Builder.PersonBuilder builder); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "firstName")] + internal static extern ref string SetFirstNameField(global::Builder.PersonBuilder builder); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ValidateName")] + internal static extern void Validate_SetLastName_ValidateName_0(global::Builder.PersonBuilder owner, string value); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ValidateName")] + internal static extern void Validate_SetFirstName_ValidateName_0(global::Builder.PersonBuilder owner, string value); + + [UnsafeAccessor(UnsafeAccessorKind.Method)] + internal static extern global::Builder.Person Build(global::Builder.PersonBuilder builder); + +} + +public sealed class TypedPersonBuilder + where TLastNameState : global::TypedStateBuilder.IValueState + where TFirstNameState : global::TypedStateBuilder.IValueState +{ + private global::Builder.PersonBuilder Inner \{ get; } + + internal TypedPersonBuilder(global::Builder.PersonBuilder inner) + { + Inner = inner; + } + + internal static TypedPersonBuilder SetLastNameCore(TypedPersonBuilder builder, string value) + where TFirstNameState : global::TypedStateBuilder.IValueState + { + PersonBuilder_Accessors.SetLastNameField(builder.Inner) = value; + return new TypedPersonBuilder(builder.Inner); + } + + internal static TypedPersonBuilder SetFirstNameCore(TypedPersonBuilder builder, string value) + where TLastNameState : global::TypedStateBuilder.IValueState + { + PersonBuilder_Accessors.SetFirstNameField(builder.Inner) = value; + return new TypedPersonBuilder(builder.Inner); + } + + internal static global::Builder.Person BuildCore(TypedPersonBuilder builder) + { + List? exceptions = null; + + try + { + PersonBuilder_Accessors.Validate_SetLastName_ValidateName_0(builder.Inner, PersonBuilder_Accessors.SetLastNameField(builder.Inner)); + } + catch (Exception ex) + { + (exceptions ??= new List()).Add(ex); + } + + try + { + PersonBuilder_Accessors.Validate_SetFirstName_ValidateName_0(builder.Inner, PersonBuilder_Accessors.SetFirstNameField(builder.Inner)); + } + catch (Exception ex) + { + (exceptions ??= new List()).Add(ex); + } + + if (exceptions is not null) + { + throw new AggregateException(exceptions); + } + + return PersonBuilder_Accessors.Build(builder.Inner); + } + +} + +public static partial class TypedPersonBuilderExtensions +{ + /// + /// Sets the lastName. + /// + /// The builder. + /// The value for lastName. + /// + /// The updated builder. + /// + public static TypedPersonBuilder SetLastName(this TypedPersonBuilder builder, string value) + where TFirstNameState : global::TypedStateBuilder.IValueState + => TypedPersonBuilder.SetLastNameCore(builder, value); + + /// + /// Sets the firstName. + /// + /// The builder. + /// The value for firstName. + /// + /// The updated builder. + /// + public static TypedPersonBuilder SetFirstName(this TypedPersonBuilder builder, string value) + where TLastNameState : global::TypedStateBuilder.IValueState + => TypedPersonBuilder.SetFirstNameCore(builder, value); + + /// + /// Builds the result. + /// + /// The builder. + /// + /// The built result. + /// + /// Thrown if validation fails. + public static global::Builder.Person Build(this TypedPersonBuilder builder) + => TypedPersonBuilder.BuildCore(builder); + +} + +} + +namespace TypedStateBuilder +{ + +public static partial class TypedStateBuilders +{ + /// + /// Creates a new . + /// + /// + /// A new builder instance. + /// + public static global::Builder.TypedPersonBuilder CreatePersonBuilder() + { + var inner = global::Builder.PersonBuilder_Accessors.Create(); + return new global::Builder.TypedPersonBuilder(inner); + } + +} +} + +``` + + + + +```csharp showLineNumbers +// +#nullable enable +using System; + +namespace TypedStateBuilder; + +/// +/// Represents a builder step state. +/// +public interface IValueState \{ } + +/// +/// Indicates that a builder step has been set. +/// +public sealed class ValueSet : IValueState \{ } + +/// +/// Indicates that a builder step has not been set. +/// +public sealed class ValueUnset : IValueState \{ } + +/// +/// Marks a method as a build method. +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +public sealed class BuildAttribute : Attribute +{ + /// + /// Marks a method as a build method. + /// + public BuildAttribute() + { + } + + /// + /// Marks a method as a build method for a specific branch. + /// + /// The branch path. + public BuildAttribute(string targetBranch) + { + } +} + +/// +/// Marks a field as a builder step. +/// +[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] +public class StepForValueAttribute : Attribute +{ + /// + /// Marks a required builder step. + /// + public StepForValueAttribute() + { + } + + /// + /// Marks an optional builder step with a default value provider. + /// + /// The provider member name. + public StepForValueAttribute(string providerMemberName) + { + } +} + +/// +/// Assigns a step to a branch. +/// +[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] +public sealed class StepBranchAttribute : Attribute +{ + /// + /// Assigns a step to a branch. + /// + /// The branch path. + public StepBranchAttribute(string branchPath) + { + } +} + +/// +/// Adds another way to set a builder step. +/// +[AttributeUsage(AttributeTargets.Field, AllowMultiple = true, Inherited = false)] +public sealed class StepOverloadAttribute : Attribute +{ + /// + /// Adds another way to set a builder step. + /// + /// The overload member name. + public StepOverloadAttribute(string overloadMemberName) + { + } +} + +/// +/// Adds validation for a builder step. +/// +[AttributeUsage(AttributeTargets.Field, AllowMultiple = true, Inherited = false)] +public sealed class ValidateValueAttribute : Attribute +{ + /// + /// Adds validation for a builder step. + /// + /// The validator member name. + public ValidateValueAttribute(string validatorMemberName) + { + } +} + +/// +/// Enables typed builder generation for a builder class. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public sealed class TypedStateBuilderAttribute : Attribute +{ +} + +``` + + + + +## Useful + +### Download Example (.NET C#) + +:::tip + +[Download Example project TypedStateBuilder.Generator ](/sources/TypedStateBuilder.Generator.zip) + +::: + + +### Share TypedStateBuilder.Generator + + + +https://ignatandrei.github.io/RSCG_Examples/v2/docs/TypedStateBuilder.Generator + + + diff --git a/v2/rscg_examples_site/docs/RSCG-Examples/index.md b/v2/rscg_examples_site/docs/RSCG-Examples/index.md index 5a86bdca1..0588215a3 100644 --- a/v2/rscg_examples_site/docs/RSCG-Examples/index.md +++ b/v2/rscg_examples_site/docs/RSCG-Examples/index.md @@ -1,7 +1,7 @@ --- sidebar_position: 30 -title: 270 RSCG list by category -description: 270 RSCG list by category +title: 271 RSCG list by category +description: 271 RSCG list by category slug: /rscg-examples --- @@ -206,7 +206,7 @@ import DocCardList from '@theme/DocCardList'; ## Builder
- Expand Builder =>examples:8 + Expand Builder =>examples:9 @@ -247,6 +247,11 @@ import DocCardList from '@theme/DocCardList'; [OrderedBuildersGenerator](/docs/OrderedBuildersGenerator) + + + +[TypedStateBuilder.Generator](/docs/TypedStateBuilder.Generator) +
@@ -1762,6 +1767,8 @@ flowchart LR; Builder--> OrderedBuildersGenerator((OrderedBuildersGenerator)) + Builder--> TypedStateBuilder.Generator((TypedStateBuilder.Generator)) + Clone--> CopyTo((CopyTo)) Clone--> Dolly((Dolly)) diff --git a/v2/rscg_examples_site/docs/about.md b/v2/rscg_examples_site/docs/about.md index 241e4b943..527de206d 100644 --- a/v2/rscg_examples_site/docs/about.md +++ b/v2/rscg_examples_site/docs/about.md @@ -6,7 +6,7 @@ title: About ## Content You will find here code examples -of 270 Roslyn Source Code Generator (RSCG) +of 271 Roslyn Source Code Generator (RSCG) that can be useful for you. That means, you will write more elegant and concise code - even if the generators code is not always nice to look. ## Are those examples ready for production? diff --git a/v2/rscg_examples_site/docs/indexRSCG.md b/v2/rscg_examples_site/docs/indexRSCG.md index f1581a1d7..a6787b3a8 100644 --- a/v2/rscg_examples_site/docs/indexRSCG.md +++ b/v2/rscg_examples_site/docs/indexRSCG.md @@ -7,9 +7,9 @@ slug: /List-of-RSCG import useBaseUrl from '@docusaurus/useBaseUrl'; -## 270 RSCG with examples in descending chronological order +## 271 RSCG with examples in descending chronological order -This is the list of 270 ( 16 from Microsoft) RSCG with examples +This is the list of 271 ( 16 from Microsoft) RSCG with examples [See by category](/docs/rscg-examples) [See as json](/exports/RSCG.json) [See as Excel](/exports/RSCG.xlsx) @@ -20,6 +20,7 @@ This is the list of 270 ( 16 from Microsoft) RSCG with examples | No | Name | Date | Category | | --------- | ----- | ---- | -------- | +|271| [TypedStateBuilder.Generator by Georgiy Petrov ](/docs/TypedStateBuilder.Generator)|2026-05-16 => 16 May 2026 | [Builder](/docs/Categories/Builder) | |270| [AssemblyMetadata.Generators by LoreSoft ](/docs/AssemblyMetadata.Generators)|2026-05-15 => 15 May 2026 | [EnhancementProject](/docs/Categories/EnhancementProject) | |269| [LinkDotNet.Enumeration by Steven Giesel ](/docs/LinkDotNet.Enumeration)|2026-05-14 => 14 May 2026 | [Enum](/docs/Categories/Enum) | |268| [GenerateDispose by Itai Tzur ](/docs/GenerateDispose)|2026-05-13 => 13 May 2026 | [Disposer](/docs/Categories/Disposer) | diff --git a/v2/rscg_examples_site/src/components/HomepageFeatures/index.js b/v2/rscg_examples_site/src/components/HomepageFeatures/index.js index c76dcb78e..5d7af9dd0 100644 --- a/v2/rscg_examples_site/src/components/HomepageFeatures/index.js +++ b/v2/rscg_examples_site/src/components/HomepageFeatures/index.js @@ -4,7 +4,7 @@ import styles from './styles.module.css'; const FeatureList = [ { -title: '270 Examples (16 from MSFT)', +title: '271 Examples (16 from MSFT)', Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, description: ( <> diff --git a/v2/rscg_examples_site/static/exports/RSCG.json b/v2/rscg_examples_site/static/exports/RSCG.json index 886e25d9a..23d74a2dd 100644 --- a/v2/rscg_examples_site/static/exports/RSCG.json +++ b/v2/rscg_examples_site/static/exports/RSCG.json @@ -2161,6 +2161,14 @@ "Source": "https://github.com/loresoft/AssemblyMetadata.Generators", "Category": "EnhancementProject", "AddedOn": "2026-05-15T00:00:00" + }, + { + "Name": "TypedStateBuilder.Generator", + "Link": "https://ignatandrei.github.io/RSCG_Examples/v2/docs/TypedStateBuilder.Generator", + "NuGet": "https://www.nuget.org/packages/TypedStateBuilder.Generator/", + "Source": "https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator", + "Category": "Builder", + "AddedOn": "2026-05-16T00:00:00" } ] } \ No newline at end of file From 15afc50385ce40c7505c366918445f055ef9ffc6 Mon Sep 17 00:00:00 2001 From: Andrei Ignat Date: Fri, 29 May 2026 07:01:22 +0300 Subject: [PATCH 2/3] fosc1 --- .../examples/TypedStateBuilder.Generator.html | 64 +++++++++++++++ v2/book/list.html | 6 +- v2/book/pandocHTML.yaml | 1 + v2/docFind.json | 6 ++ .../TypedStateBuilder.Generator.md | 74 +++++++++--------- .../sources/TypedStateBuilder.Generator.zip | Bin 0 -> 1491 bytes 6 files changed, 113 insertions(+), 38 deletions(-) create mode 100644 v2/book/examples/TypedStateBuilder.Generator.html create mode 100644 v2/rscg_examples_site/static/sources/TypedStateBuilder.Generator.zip diff --git a/v2/book/examples/TypedStateBuilder.Generator.html b/v2/book/examples/TypedStateBuilder.Generator.html new file mode 100644 index 000000000..26bebbf51 --- /dev/null +++ b/v2/book/examples/TypedStateBuilder.Generator.html @@ -0,0 +1,64 @@ + +

RSCG nr 271 : TypedStateBuilder.Generator

+ +

Info

+Nuget : https://www.nuget.org/packages/TypedStateBuilder.Generator/ + +

You can find more details at : https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator

+ +

Author :Georgiy Petrov

+ +

Source: https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator

+ +

About

+ +Generate strongly typed state builders for C# applications, improving code readability and maintainability. + +

+ How to use +

+

+ Add reference to the TypedStateBuilder.Generator in the csproj +

+ + +

This was for me the starting code

+ +
+ I have coded the file Program.cs +
+ +
+ +
+ I have coded the file Person.cs +
+ +
+

And here are the generated files

+ +
+ The file generated is global__Builder_PersonBuilder_7DB9C28B.TypedStateBuilder.g.cs +
+ + +
+ The file generated is TypedStateBuilder.Attributes.g.cs +
+ + +

+ You can download the code and this page as pdf from + + https://ignatandrei.github.io/RSCG_Examples/v2/docs/TypedStateBuilder.Generator + +

+ + +

+ You can see the whole list at + + https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG + +

+ diff --git a/v2/book/list.html b/v2/book/list.html index fcf4f92b2..502a2e64f 100644 --- a/v2/book/list.html +++ b/v2/book/list.html @@ -17,7 +17,7 @@

-This is the list of 270 RSCG with examples => +This is the list of 271 RSCG with examples =>

@@ -1106,6 +1106,10 @@

+ + + +
270 AssemblyMetadata.Generators
271TypedStateBuilder.Generator
diff --git a/v2/book/pandocHTML.yaml b/v2/book/pandocHTML.yaml index fe9f8e760..795799838 100644 --- a/v2/book/pandocHTML.yaml +++ b/v2/book/pandocHTML.yaml @@ -284,6 +284,7 @@ input-files: - examples/GenerateDispose.html - examples/LinkDotNet.Enumeration.html - examples/AssemblyMetadata.Generators.html +- examples/TypedStateBuilder.Generator.html # or you may use input-file: with a single value # defaults: diff --git a/v2/docFind.json b/v2/docFind.json index 2df539039..2189955b9 100644 --- a/v2/docFind.json +++ b/v2/docFind.json @@ -1618,5 +1618,11 @@ "category": "EnhancementProject", "href": "/RSCG_Examples/v2/docs/AssemblyMetadata.Generators/", "body": "Source generator to expose assembly attributes as string constants" + }, + { + "title": "TypedStateBuilder.Generator", + "category": "Builder", + "href": "/RSCG_Examples/v2/docs/TypedStateBuilder.Generator/", + "body": "A Roslyn source generator that produces **compile-time safe builders** using the *type-state pattern*." } ] \ No newline at end of file diff --git a/v2/rscg_examples_site/docs/RSCG-Examples/TypedStateBuilder.Generator.md b/v2/rscg_examples_site/docs/RSCG-Examples/TypedStateBuilder.Generator.md index 45a5d07e8..1f2777cb1 100644 --- a/v2/rscg_examples_site/docs/RSCG-Examples/TypedStateBuilder.Generator.md +++ b/v2/rscg_examples_site/docs/RSCG-Examples/TypedStateBuilder.Generator.md @@ -49,7 +49,7 @@ Georgiy Petrov ## Original Readme :::note -# TypedStateBuilder +### TypedStateBuilder A Roslyn incremental source generator that makes **invalid builder usage impossible to compile**. @@ -59,7 +59,7 @@ You define a normal builder class. The generator produces a fluent API where **i --- -## Why +###### Why Traditional builders rely on: @@ -87,7 +87,7 @@ You write the builder once. The generator handles the rest. --- -## What this solves +###### What this solves TypedStateBuilder enforces correct builder usage while keeping the flexibility of a fluent API: @@ -106,7 +106,7 @@ Result: **invalid builder usage becomes unrepresentable code**, instead of somet --- -## Comparison +###### Comparison | Feature | Simple Builder | Interface Step Builder | TypedStateBuilder | | ----------------------- | -------------- | ---------------------- | ----------------- | @@ -122,9 +122,9 @@ Result: **invalid builder usage becomes unrepresentable code**, instead of somet --- -## Example +###### Example -### Builder template +######### Builder template ```csharp [TypedStateBuilder] @@ -165,7 +165,7 @@ public class UserBuilder } ``` -### Usage +######### Usage ```csharp var user = TypedStateBuilders @@ -192,7 +192,7 @@ var invalid = TypedStateBuilders --- -## What you write vs what you get +###### What you write vs what you get You only write: @@ -217,7 +217,7 @@ No interfaces, no manual state tracking, no boilerplate. --- -## How it works +###### How it works Each step is encoded as a **type-state transition**: @@ -235,7 +235,7 @@ TypedBuilder A build method becomes available only when all required states for that build path are `ValueSet`. -### Step semantics +######### Step semantics Each step: @@ -247,9 +247,9 @@ The underlying builder remains mutable, but repeated assignments are not express --- -## Branching +###### Branching -### Mental model +######### Mental model Think of branches like paths: @@ -267,7 +267,7 @@ This keeps related steps together while preventing invalid combinations. --- -### Example +######### Example ```csharp [TypedStateBuilder] @@ -306,9 +306,9 @@ public class VehicleBuilder --- -### Branch semantics +######### Branch semantics -#### Build requirement +########## Build requirement If any step uses branching, **all build methods must specify an explicit branch target**: @@ -321,7 +321,7 @@ Unbranched `[Build]` methods are not allowed in branched builders. --- -#### Step applicability +########## Step applicability A step applies if: @@ -331,7 +331,7 @@ A step applies if: --- -#### Step compatibility +########## Step compatibility Two steps are compatible if: @@ -342,13 +342,13 @@ Sibling branches are incompatible. --- -#### Ancestor requirement +########## Ancestor requirement If a step belongs to a deeper branch, any declared ancestor steps must already be set before it is callable. --- -## Step overloads +###### Step overloads ```csharp [StepForValue] @@ -368,14 +368,14 @@ builder.SetName("Alice", "Walker"); --- -## Optional values and defaults +###### Optional values and defaults ```csharp [StepForValue(nameof(DefaultAge))] private int _age; ``` -### Behavior +######### Behavior * step becomes optional * if unset, default runs during build @@ -385,14 +385,14 @@ Defaults are applied before validation and build execution. --- -## Validation +###### Validation ```csharp [ValidateValue(nameof(ValidateName))] private string _name; ``` -### Behavior +######### Behavior * runs automatically before build * runs only for steps applicable to the selected build path @@ -402,7 +402,7 @@ private string _name; throw new AggregateException(...) ``` -### Execution details +######### Execution details * defaults are applied first * validators run next @@ -415,7 +415,7 @@ Note: --- -## Build methods +###### Build methods ```csharp [Build] @@ -429,7 +429,7 @@ or: public Vehicle BuildCar() ``` -### Behavior +######### Behavior A build method: @@ -439,7 +439,7 @@ A build method: --- -## What gets generated +###### What gets generated For each builder: @@ -452,7 +452,7 @@ For each builder: --- -## Constructors +###### Constructors Constructors are exposed via: @@ -466,7 +466,7 @@ TypedStateBuilders.CreateMyBuilder(...) --- -## Dependency Injection +###### Dependency Injection Constructor dependencies can be used in: @@ -477,7 +477,7 @@ Constructor dependencies can be used in: --- -## Performance +###### Performance * incremental generator (fast IDE experience) * no reflection @@ -494,9 +494,9 @@ Notes: --- -## Constraints and limitations +###### Constraints and limitations -### Builder +######### Builder * class only * non-nested @@ -504,30 +504,30 @@ Notes: * no inheritance * public or internal -### Steps +######### Steps * fields only * must be mutable * no static or readonly -### Branching +######### Branching * path-based, prefix matching * explicit build targets required when used -### Step overloads +######### Step overloads * must be non-generic * must return field type * must not collide -### Validation +######### Validation * only `void` or `Task` supported --- -## Summary +###### Summary TypedStateBuilder generates a builder API where: diff --git a/v2/rscg_examples_site/static/sources/TypedStateBuilder.Generator.zip b/v2/rscg_examples_site/static/sources/TypedStateBuilder.Generator.zip new file mode 100644 index 0000000000000000000000000000000000000000..7d5ddef3639a2f7628bde1721251fc32ef40a9ce GIT binary patch literal 1491 zcmWIWW@Zs#U|`^25G|P!J^$$W0COPE4TyPw*r_x#CndE=uQ(^KVl(Ht^P0LR&z#@< z_-RnU22PDr=e>OO0y#W%PHqS-(mH#_(^L0S`=zRrM_C;zRat8nBuUq*%w-6GTOeJq zCnkHJ(pzgr28MVh1_m*N1^O_Hl8Xz9^0Qil4*D?}3fMXyvN!2v-E}s$cV;i(JS8+w zH!q0Ug&%O33&Y4RsXX6hxDd9&Kj;?z(WBan-J3>klPkUt^(3EiS z4DhK5S>AH)^nb@2lP%BP;GOMR<>Bw=zNu}d;sGJ9r<(RBH}8I#tMhMa#{1XPIn!C5 zieJj?n0dy$i1+#iCYH&}vuDQaW)hNHw?AS+zEP2vctharD@y6+4O=&1`Z zr(KU-%G`_!DViAehpW3X*Yl4$wCnUzA5e*E*=)XUj)>C!2)npDT6YRmd5>n$%^Y`=E~0(bMbh1`K2V$2n`Kqw;*SFB6wa<( zveIlbvtG}X42FAB%Nu(7WPE?$EZP6Iwp7@EpXJ4MPP^?3)hlWqZ`rzYhk5A*|I0;| z1^r5fqDHT!&b1p_#Is#`Kj;1TpDQ&Y|TR|CBCyjZmYH9T~+&YchTXwnG1#G>`=sg}1+kVe3Uqb93nO!sEw z_%i31IzPY_8bw~A#y`z zhNeS$Lu}K_4@|b(FU^tJz&K0cIukINF*1oT;4XoHenSGVVgghiA?w6ev>>!40+~>q zXaxR0EU@?$UVsVv1Jp4eo0_~2h$JFG|1Yq#U?`gMqs{!Ylp{efHx}} QNDV6x+5#Qezyjg{0Q)yCssI20 literal 0 HcmV?d00001 From 5cd5876198b195ada25131b46d85e9df4558efef Mon Sep 17 00:00:00 2001 From: Andrei Ignat Date: Fri, 29 May 2026 07:07:21 +0300 Subject: [PATCH 3/3] more desc --- .../description.json | 47 +++++- .../TypedStateBuilder.Generator/video.json | 2 +- .../TypedStateBuilder.Generator.md | 139 +++++++++++++++++- .../static/exports/RSCG.xlsx | Bin 14194 -> 14242 bytes 4 files changed, 184 insertions(+), 4 deletions(-) diff --git a/v2/rscg_examples/TypedStateBuilder.Generator/description.json b/v2/rscg_examples/TypedStateBuilder.Generator/description.json index 9884d846f..9c3ca6985 100644 --- a/v2/rscg_examples/TypedStateBuilder.Generator/description.json +++ b/v2/rscg_examples/TypedStateBuilder.Generator/description.json @@ -9,7 +9,52 @@ "source":"https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator" }, "data":{ - "goodFor":["Generate strongly typed state builders for C# applications, improving code readability and maintainability."], + "goodFor":["Generate strongly typed state builders for C# applications, enforced at compile time", + "**Summary of TypedStateBuilder.Generator**", +"", +"**Purpose:** Generates compile-time safe step-by-step builders using the *type-state pattern* — each required property must be set in order before Build() is available, enforced at compile time.", +"", +"**NuGet:** https://www.nuget.org/packages/TypedStateBuilder.Generator/", +"**GitHub:** https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator", +"**Author:** Georgiy Petrov", +"", +"**How to use:**", +"", +"1. Decorate a builder class with [TypedStateBuilder], mark each required step with [StepForValue], and optionally add [ValidateValue] for validation:", +"```csharp", +"[TypedStateBuilder]", +"public class PersonBuilder", +"{", +" [StepForValue]", +" [ValidateValue(nameof(ValidateName))]", +" private string lastName = string.Empty;", +"", +" [StepForValue]", +" [ValidateValue(nameof(ValidateName))]", +" private string firstName = string.Empty;", +"", +" public void ValidateName(string name)", +" {", +" if (string.IsNullOrWhiteSpace(name) || name.Length <= 1)", +" throw new ArgumentException(\"Name must be at least 2 characters long.\");", +" }", +"", +" [Build]", +" public Person Build() => new Person(firstName, lastName);", +"}", +"```", +"", +"2. Use the generated fluent builder — steps are enforced in order at compile time:", +"```csharp", +"var p = TypedStateBuilders", +" .CreatePersonBuilder()", +" .SetFirstName(\"Andrei\")", +" .SetLastName(\"Ignat\")", +" .Build();", +"", +"Console.WriteLine(p.FullName()); // \"Andrei Ignat\"", +"```", +""], "csprojDemo":"Builder.csproj", "csFiles":["Program.cs","Person.cs"], "excludeDirectoryGenerated":[""], diff --git a/v2/rscg_examples/TypedStateBuilder.Generator/video.json b/v2/rscg_examples/TypedStateBuilder.Generator/video.json index c283f3bf1..6d4d51733 100644 --- a/v2/rscg_examples/TypedStateBuilder.Generator/video.json +++ b/v2/rscg_examples/TypedStateBuilder.Generator/video.json @@ -8,7 +8,7 @@ {"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG"}, {"typeStep":"text","arg": "My name is Andrei Ignat and I am deeply fond of Roslyn Source Code Generator. "}, -{"typeStep":"text","arg": "Today I will present TypedStateBuilder.Generator . Generate strongly typed state builders for C# applications, improving code readability and maintainability. ."}, +{"typeStep":"text","arg": "Today I will present TypedStateBuilder.Generator . Generate strongly typed state builders for C# applications, enforced at compile time**Summary of TypedStateBuilder.Generator****Purpose:** Generates compile-time safe step-by-step builders using the *type-state pattern* — each required property must be set in order before Build() is available, enforced at compile time.**NuGet:** https://www.nuget.org/packages/TypedStateBuilder.Generator/**GitHub:** https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator**Author:** Georgiy Petrov**How to use:**1. Decorate a builder class with [TypedStateBuilder], mark each required step with [StepForValue], and optionally add [ValidateValue] for validation:```csharp[TypedStateBuilder]public class PersonBuilder{ [StepForValue] [ValidateValue(nameof(ValidateName))] private string lastName = string.Empty; [StepForValue] [ValidateValue(nameof(ValidateName))] private string firstName = string.Empty; public void ValidateName(string name) { if (string.IsNullOrWhiteSpace(name) || name.Length <= 1) throw new ArgumentException("Name must be at least 2 characters long."); } [Build] public Person Build() => new Person(firstName, lastName);}```2. Use the generated fluent builder — steps are enforced in order at compile time:```csharpvar p = TypedStateBuilders .CreatePersonBuilder() .SetFirstName("Andrei") .SetLastName("Ignat") .Build();Console.WriteLine(p.FullName()); // "Andrei Ignat"``` ."}, {"typeStep":"browser","arg":"https://www.nuget.org/packages/TypedStateBuilder.Generator/"}, {"typeStep":"text","arg": "The whole example is here"}, {"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/TypedStateBuilder.Generator"}, diff --git a/v2/rscg_examples_site/docs/RSCG-Examples/TypedStateBuilder.Generator.md b/v2/rscg_examples_site/docs/RSCG-Examples/TypedStateBuilder.Generator.md index 1f2777cb1..705254022 100644 --- a/v2/rscg_examples_site/docs/RSCG-Examples/TypedStateBuilder.Generator.md +++ b/v2/rscg_examples_site/docs/RSCG-Examples/TypedStateBuilder.Generator.md @@ -1,7 +1,7 @@ --- sidebar_position: 2710 title: 271 - TypedStateBuilder.Generator -description: Generate strongly typed state builders for C# applications, improving code readability and maintainability. +description: Generate strongly typed state builders for C# applications, enforced at compile time slug: /TypedStateBuilder.Generator --- import Tabs from '@theme/Tabs'; @@ -544,7 +544,142 @@ You define a builder. The generator makes its correct usage explicit. ### About :::note -Generate strongly typed state builders for C# applications, improving code readability and maintainability. +Generate strongly typed state builders for C# applications, enforced at compile time + + +**Summary of TypedStateBuilder.Generator** + + + + + +**Purpose:** Generates compile-time safe step-by-step builders using the *type-state pattern* — each required property must be set in order before Build() is available, enforced at compile time. + + + + + +**NuGet:** https://www.nuget.org/packages/TypedStateBuilder.Generator/ + + +**GitHub:** https://github.com/Georgiy-Petrov/TypedStateBuilder.Generator + + +**Author:** Georgiy Petrov + + + + + +**How to use:** + + + + + +1. Decorate a builder class with [TypedStateBuilder], mark each required step with [StepForValue], and optionally add [ValidateValue] for validation: + + +```csharp + + +[TypedStateBuilder] + + +public class PersonBuilder + + +{ + + + [StepForValue] + + + [ValidateValue(nameof(ValidateName))] + + + private string lastName = string.Empty; + + + + + + [StepForValue] + + + [ValidateValue(nameof(ValidateName))] + + + private string firstName = string.Empty; + + + + + + public void ValidateName(string name) + + + { + + + if (string.IsNullOrWhiteSpace(name) || name.Length <= 1) + + + throw new ArgumentException("Name must be at least 2 characters long."); + + + } + + + + + + [Build] + + + public Person Build() => new Person(firstName, lastName); + + +} + + +``` + + + + + +2. Use the generated fluent builder — steps are enforced in order at compile time: + + +```csharp + + +var p = TypedStateBuilders + + + .CreatePersonBuilder() + + + .SetFirstName("Andrei") + + + .SetLastName("Ignat") + + + .Build(); + + + + + +Console.WriteLine(p.FullName()); // "Andrei Ignat" + + +``` + + + ::: diff --git a/v2/rscg_examples_site/static/exports/RSCG.xlsx b/v2/rscg_examples_site/static/exports/RSCG.xlsx index 45d3074e4e449031c54feb292cfe378dab9d3352..b3f3492cdba8ae4037fd207ef244c78387a4308d 100644 GIT binary patch delta 895 zcmeyAwlfYcp{_IoI#QY2Gcf|H- zBvs$>l{)WW+aOl#5_kNQo@n3KZ*2(^lRfMAW+>G79-ou0bvtmc_Gx3z?;IHqK1UyF z|Ddp?&OX@xSpL~Z=~rELJbM2B?|(*^a~QxufBf|C{LP6*(u@Mh#zqFo#s-$=hG{7# z#)$@#dyM74Zd~O3b?Pl(tn3BGjugY>|7J4vTPOPZF&px@trx01pK{A@Ifv=nHs0H- z#6MWflP^{4d?a#f^Tr1rF^vrZuRL^aY}oYYW?j{!`|Q7$nG;-H&j0)Fw*$({_9*@paCs`M&h~4{q2_B&w*DHe{BJ%j`C52r%IzC38%hrK zechy!Et{VqX^_6^vFV5Sj(xf8kKFyAcgFr=U85T7E*9Y4BdgB*%cw5hHLbB~6C0m* zO!(JhMv*yMI)YRFnJ*NV_rGMkeCK8VIaBRVKXS{zJ3)2#p=ImBMK3?|_CG&w!*2#y z@PJe1=6xnwjGXW!I{BliNfd|&jznN^Sh6rM@B)Jd0bT={z~oz8Qkj!l3`)EK-i%Bl z42ZOj8si9!3=CT)*PE$xfQ$!4%H(xsVperV3=Hu_sX4{^dLRQg?QUL%_Xl#H0 delta 840 zcmZ3K|0%CNz?+#xgn@y9gW<60o*4V%i_G={c}k274E#X4B1gYGzbHE?KR;WqA~&bE z-;?i<0gvl?p-O(u=77+xsfLuEpyspVw9SxScuzc>q2O&Pm z7M^lUtEO2Wc~NC~e$&*FQ&kmzs+avyeK_?n7vJ%-d`FCLZuew&(a32!yz1oV$iD8h znOnUVi+}jW@xNk%Vc6qoUyd&9*Pqz1w`-+wd0B$MCcUTrS`U}!*RrE}^;CSrr+JJU z|Lzm8eZ=HD1@2~++TfFM0xmHUn zH@KH>lyEksJ$u1^fxTNNFLcj8$G!6pw?y#1b5j~*OJp>(51jrpH?)=Y&C2i%@0UDX zBgG?1!Mx#VR1=iPHHhI6$W@SGKnxC5-lR? zm>47w`hcv>lh>Q6bAXHo#mVG*W@1+UMhpz`MX5Q(`g$M|-7M6IK{jl`WIc0DW}sP< Tqs*llH%=}yS7W