Skip to content

Add StuntReference, Stunt.For/Get and StuntBuilder - #252

Merged
kzu merged 2 commits into
mainfrom
dev/stunt-builder
Sep 30, 2026
Merged

kzu merged 2 commits into
mainfrom
dev/stunt-builder

Conversation

@kzu

@kzu kzu commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Adds two related ways to configure stunt behaviors beyond the bare Stunt.Of<T>.

StuntReference<T> / Stunt.For / Stunt.Get

Stunt.For<T>(...) returns a StuntReference<T> that implements IStunt, so AddBehavior/InsertBehavior apply to it directly and the concrete type flows through for chaining. The stunt is not constructed until the first ToObject() (the implicit conversion calls ToObject() too). That call installs the behaviors configured so far with BehaviorPipelineFactory.UseAmbient, so virtual members invoked from a base constructor are intercepted. If none were added, the factory already current at that call is used. After construction, Behaviors forwards to the stunt's own pipeline.

Stunt.Get(stunt) still wraps an already created stunt and forwards Behaviors immediately.

var stunt = Stunt.For<CtorCaller>().AddBehavior((invocation, next) =>
    invocation.MethodBase.Name == nameof(CtorCaller.Name)
        ? invocation.CreateValueReturn("proxy")
        : next(invocation, next));

CtorCaller created = stunt.ToObject(); // Seen == "proxy"

An implicit conversion to T is declared too, but C# does not allow user-defined conversions to interface types, so it only applies to class and delegate stunts, which is why Stunt.Of<T> keeps returning T.

Behaviors configured before ToObject() also see the constructor invocation itself. A behavior that only understands a particular method should skip constructors (appliesTo) or call next.

StuntBuilder

Stunt.Builder() returns a StuntBuilder that collects behaviors (same AddBehavior/InsertBehavior extension methods, since it implements IStunt) and applies them to every stunt it builds. Its Build<T> overloads mirror Stunt.Of<T> one to one, including the delegate overload and the T1..T8 extra interfaces.

var builder = Stunt.Builder()
    .AddBehavior(new RecordingBehavior())
    .AddBehavior(new DefaultValueBehavior());

ICalculator calculator = builder.Build<ICalculator>();
IStore store = builder.Build<IStore>();

Each Build wraps creation in BehaviorPipelineFactory.UseAmbient with a factory that seeds the new pipeline from the builder behaviors, so the same behaviors apply during construction of every stunt the builder creates.

BehaviorPipeline copies the enumerable it is given, so every built stunt gets a snapshot of the behaviors at build time while sharing the behavior instances (a single RecordingBehavior records all stunts from that builder).

Notes

  • Every factory overload (Of, For, Build) carries [StuntGenerator], which is what the source generator keys off to decide which stunt types to generate from the call site's type arguments. This works for instance methods too, so builder.Build<T>() participates in compile-time generation.
  • src/Stunts.Package/Stunt.vb mirrors the C# content file. VB cannot constrain a type parameter to System.Delegate (BC32061), so the delegate overloads are unconstrained; behavior is identical. There is no VB project in the solution, so the file was validated by compiling it in a scratch Option Strict On project against Stunts.dll.
  • Covered by src/Stunts.UnitTests/Scenarios/StuntBuilder.cs and a class-proxy scenario that configures a behavior on Stunt.For before ToObject().
  • readme.md and AGENTS.md document the API and the design decisions.

@kzu kzu added the enhancement New feature or request label Sep 30, 2026
@kzu
kzu enabled auto-merge (rebase) September 30, 2026 22:04
@kzu
kzu merged commit 9af8999 into main Sep 30, 2026
6 checks passed
@kzu
kzu deleted the dev/stunt-builder branch September 30, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant