Skip to content

BodyBuilder

Build method, constructor, and property bodies with a fluent API for control flow, statements, and comments.

See also: Emit Overview | MethodBuilder | TypeRef & ExpressionRef | CatchBuilder | SwitchStatementBuilder | SwitchExpressionBuilder | CommentBuilder

Factory Methods

Method Description
Empty() Create an empty body

Statements

body.AddStatement("Console.WriteLine(\"Hello\")")
body.AddStatements("var a = 1;\nvar b = 2;")  // multi-line string
body.AddReturn("result")
body.AddReturn()                                // void return
body.AddThrow("new InvalidOperationException()")
body.AddCustom(statementSyntax)                 // raw Roslyn StatementSyntax

Comments

body.AddComment("// Dispatch the event")
body.AddComment(c => c
    .Line("First line")
    .Line("Second line"))

Comments are attached as leading trivia on the next statement added.

Branching

If / If-Else

body.AddIf("user != null", b => b
    .AddStatement("Process(user)"))

body.AddIfElse("user != null",
    ifBody => ifBody.AddStatement("Process(user)"),
    elseBody => elseBody.AddThrow("new ArgumentNullException()"))

Switch Statement

Traditional switch with case sections. Case bodies containing variable declarations are automatically wrapped in braces.

body.AddSwitch("job.JobType", sw => sw
    .Case("\"create\"", cb => cb
        .AddStatement("HandleCreate()")
        .AddStatement("break"))
    .Case("MyEvent e", cb => cb
        .AddStatement("var payload = e.GetPayload()")
        .AddStatement("await Process(payload)")
        .AddReturn("true"))
    .Default(cb => cb.AddReturn("false")))

Switch Expression

Emits return expr switch { ... };. Supports pattern matching arms, a discard default, and nested switches.

// Simple type-pattern switch
body.AddSwitchExpression("@event", sw => sw
    .Arm("OrderCreated e", "HandleCreated(e)")
    .Arm("OrderShipped e", "HandleShipped(e)")
    .Default("Eff<Unit>.Pure(unit)"))

// Nested switch expression (e.g., dispatch on payload then sub-dispatch on action)
body.AddSwitchExpression("payload", sw => sw
    .Arm("BlockActionsPayload ba", "ba.Actions switch", nested => nested
        .Arm("[{ ActionId: \"approve\" } action, ..]", "HandleApprove(action)")
        .Default("unitEff"))
    .Default("new Acknowledge()"))

Loops

ForEach

body.AddForEach("item", "items", b => b
    .AddStatement("Process(item)"))

While

body.AddWhile("!client.IsConnected && !token.IsCancellationRequested", b => b
    .AddStatement("await Task.Delay(500, token)"))

For

body.AddFor("var i = 0", "i < count", "i++", b => b
    .AddStatement("Process(items[i])"))

Do-While

body.AddDoWhile("hasMore", b => b
    .AddStatement("item = ReadNext()")
    .AddStatement("hasMore = item != null"))

Yield

body.AddYieldReturn("item.Transform()")
body.AddYieldBreak()

Lock

body.AddLock("_syncRoot", b => b
    .AddStatement("_count++"))

Error Handling

Try / Catch

body.AddTryCatch(
    tryBody => tryBody
        .AddStatement("await DoWork()"),
    catches => catches
        .Catch("Exception", "ex", cb => cb
            .AddStatement("logger.LogError(ex, \"Error\")"))
        .CatchAll(cb => cb
            .AddStatement("logger.LogError(\"Unknown error\")")))

Try / Catch / Finally

body.AddTryCatchFinally(
    tryBody => tryBody.AddStatement("connection.Open()"),
    catches => catches.Catch("Exception", "ex", cb => cb
        .AddStatement("logger.LogError(ex, \"Connection error\")")),
    finallyBody => finallyBody.AddStatement("connection.Close()"))

The CatchBuilder supports:

Method Description
Catch(TypeRef type, string name, ...) Catch a typed exception
CatchAll(...) Parameterless catch clause
CatchWhen(TypeRef type, string name, string condition, ...) Catch with when filter

Resource Management

Block-Scoped Using

body.AddUsing("var scope = factory.CreateScope()", b => b
    .AddStatement("var service = scope.ServiceProvider.GetService<IMyService>()")
    .AddStatement("await service.DoWork()"))

For declaration-style usings (using var x = ...), use AddStatement directly:

body.AddStatement("using var scope = factory.CreateScope()")

Await Using

body.AddAwaitUsing("var conn = await GetConnectionAsync()", b => b
    .AddStatement("await conn.ExecuteAsync(sql)"))

For declaration-style (await using var x = ...), use AddStatement directly.

Local Functions

body.AddLocalFunction(m => m
    .AsStatic()
    .WithReturnType("int")
    .AddParameter("a", "int")
    .AddParameter("b", "int")
    .WithExpressionBody("a + b"), "Add")

The configure callback receives a MethodBuilder. Access modifiers are omitted automatically (local functions don't have them).

Conditional Compilation

Wrap statements in #if/#endif directives:

body.When(Directive.Net8OrGreater, b => b
    .AddStatement("IParsable<T>.TryParse(input, out var result)"))

Properties

Property Type Description
IsEmpty bool Whether the body has no statements
Statements ImmutableArray<StatementSyntax> The collected statements
PendingTrivia ImmutableArray<SyntaxTrivia> Comments waiting to attach