Skip to content

Emit

Fluent builders for generating compilable C# code.

See also: Queries | Projections | Extensions | Roslyn Toolkit README

Overview

Emit builders construct Roslyn syntax trees using a fluent, immutable API. Where queries find code, emit builders create it.

Builder Purpose
TypeBuilder Create classes, interfaces, structs, records
MethodBuilder Create methods
PropertyBuilder Create properties
FieldBuilder Create fields
ConstructorBuilder Create constructors
ParameterBuilder Create parameters
TypeParameterBuilder Create generic type parameters
BodyBuilder Create method/property bodies
AttributeBuilder Create attributes
XmlDocumentationBuilder Create XML doc comments

All builders are immutable — each method returns a new instance.

var result = TypeBuilder
    .Class("Customer")
    .InNamespace("MyApp.Domain")
    .AsPartial()
    .AddProperty("Name", "string", p => p
        .WithAccessibility(Accessibility.Public)
        .WithAutoPropertyAccessors())
    .Emit();

if (result.IsValid(out var valid))
{
    string code = valid.Code;                    // Formatted C# code
    CompilationUnitSyntax syntax = valid.Syntax; // Roslyn syntax tree
}

Parse API

Build from natural C# signatures — parse the signature, then customize with builder methods:

// Parse a method signature, then add the body
var method = MethodBuilder.Parse("public async Task<bool> ProcessAsync(string input, CancellationToken ct = default)")
    .WithBody(b => b
        .AddStatement("await Task.Delay(100, ct)")
        .AddReturn("true"));

// Parse a property, optionally customize further
var property = PropertyBuilder.Parse("public string Name { get; set; }")
    .WithAttribute("Required");

// Parse a field
var field = FieldBuilder.Parse("private readonly ILogger _logger");

// Parse a type signature with base types, then add members
var service = TypeBuilder.Parse("public sealed class CustomerService : ICustomerService")
    .InNamespace("MyApp.Services")
    .AddField(field)
    .AddProperty(property)
    .AddMethod(method);

What Parse Handles

Parsed from Signature Added via Builder Methods
Name, type, return type Method/property bodies
Accessibility (public, private, etc.) Attributes
Modifiers (static, async, virtual, readonly, etc.) XML documentation
Parameters with modifiers and defaults Namespace and usings
Generic type parameters and constraints Additional members
Base types and interfaces
Property accessors ({ get; set; })
Field initializers

Parse Examples

// Methods with generics and constraints
MethodBuilder.Parse("public T Convert<T>(object value) where T : class")

// Abstract and virtual methods
MethodBuilder.Parse("protected virtual void OnPropertyChanged(string name)")

// Properties with various accessors
PropertyBuilder.Parse("public int Count { get; }")
PropertyBuilder.Parse("public List<string> Items { get; set; } = new()")

// Fields with modifiers
FieldBuilder.Parse("private static int _count = 0")
FieldBuilder.Parse("public const int MaxRetries = 3")

// Types with inheritance
TypeBuilder.Parse("public abstract class BaseHandler<T> : IHandler<T> where T : class")
TypeBuilder.Parse("public partial record OrderDto(string Id, decimal Total)")

TypeBuilder

Create type declarations.

Factory Methods

Method Description
Class(string name) Create a class
Interface(string name) Create an interface
Struct(string name) Create a struct
Record(string name) Create a record
Parse(string signature) Parse from signature (e.g., "public partial class MyClass")

Namespace & Usings

TypeBuilder
    .Class("MyClass")
    .InNamespace("MyApp.Domain")
    .AddUsing("System")
    .AddUsings("System.Collections.Generic", "System.Linq")

Accessibility & Modifiers

TypeBuilder
    .Class("MyClass")
    .WithAccessibility(Accessibility.Public)
    .AsStatic()
    .AsAbstract()
    .AsSealed()
    .AsPartial()

Interfaces

TypeBuilder
    .Class("Repository")
    .Implements("IRepository")
    .Implements("IDisposable", "IAsyncDisposable")

Adding Members

// Properties
builder.AddProperty("Name", "string", prop => prop
    .WithAccessibility(Accessibility.Public)
    .WithAutoPropertyAccessors())
builder.AddProperty(propertyBuilder)

// Fields
builder.AddField("_name", "string", field => field
    .WithAccessibility(Accessibility.Private)
    .AsReadonly())
builder.AddField(fieldBuilder)

// Methods
builder.AddMethod("GetName", method => method
    .WithReturnType("string")
    .WithBody(body => body.AddReturn("_name")))
builder.AddMethod(methodBuilder)

// Constructors
builder.AddConstructor(ctor => ctor
    .AddParameter("name", "string")
    .WithBody(body => body.AddStatement("_name = name;")))
builder.AddConstructor(constructorBuilder)

// Primary constructors (records, C# 12+)
builder.WithPrimaryConstructor(ctor => ctor
    .AddParameter("name", "string"))

// Nested types
builder.AddNestedType("Inner", inner => inner
    .WithAccessibility(Accessibility.Private))
builder.AddNestedType(nestedTypeBuilder)

Attributes

builder.WithAttribute("Serializable")
builder.WithAttribute("JsonProperty", attr => attr
    .WithArgument("\"name\"")
    .WithNamedArgument("Required", "true"))
builder.WithAttribute(attributeBuilder)

XML Documentation

builder.WithXmlDoc("Represents a customer entity.")

builder.WithXmlDoc(doc => doc
    .Summary("Represents a customer entity.")
    .Remarks("This class is generated by the source generator."))

builder.WithXmlDoc(existingXmlDocumentation)

Properties

builder.Name    // string — the type name
builder.Kind    // TypeKind — class, interface, struct, etc.

Emit

OptionalEmit result = builder.Emit();
OptionalEmit result = builder.Emit(EmitOptions.Default);

MethodBuilder

Create method declarations.

Factory Methods

Method Description
For(string name) Create a method with the given name
Parse(string signature) Parse from signature (e.g., "public async Task<int> GetCount()")

Return Type

method.WithReturnType("void")
method.WithReturnType("string")
method.WithReturnType("Task<int>")
method.WithReturnType("IAsyncEnumerable<Order>")

Accessibility & Modifiers

method.WithAccessibility(Accessibility.Public)
method.AsStatic()
method.AsVirtual()
method.AsOverride()
method.AsAbstract()
method.Async()

Type Parameters

method.AddTypeParameter("T")
method.AddTypeParameter("T", tp => tp
    .AsClass()
    .WithNewConstraint())
method.AddTypeParameter(typeParameterBuilder)

Parameters

method.AddParameter("name", "string")
method.AddParameter("count", "int", p => p.WithDefaultValue("0"))
method.AddParameter(parameterBuilder)

Body

// Block body
method.WithBody(body => body
    .AddStatement("Console.WriteLine(\"Starting\");")
    .AddStatement("DoWork();")
    .AddReturn("result"))

// Expression body
method.WithExpressionBody("_name")

// Append to expression body
method.AppendExpressionBody(".ToList()")

Attributes & XML Documentation

method.WithAttribute("HttpGet")
method.WithAttribute("Route", attr => attr.WithArgument("\"/api/items\""))

method.WithXmlDoc("Gets the customer name.")
method.WithXmlDoc(doc => doc
    .Summary("Gets a customer by identifier.")
    .Param("id", "The customer identifier.")
    .Returns("The customer if found; otherwise, null."))

Usings

method.AddUsing("System.Linq")
method.AddUsings("System", "System.Threading.Tasks")

Properties

method.Name             // string
method.ReturnType       // string?
method.ExtensionTargetType // string? — the type being extended (for extension methods)

PropertyBuilder

Create property declarations.

Factory Methods

Method Description
For(string name, string type) Create a property
Parse(string signature) Parse from signature (e.g., "public string Name { get; set; }")

Accessor Styles

// Auto-property { get; set; }
prop.WithAutoPropertyAccessors()

// Read-only auto-property { get; }
prop.WithAutoPropertyAccessors().AsReadOnly()

// Expression-bodied getter => expression
prop.WithGetter("_name")
prop.WithGetter("=> _name")  // "=>" is optional

// Block-bodied getter
prop.WithGetter(body => body
    .AddStatement("if (_name == null) _name = LoadName();")
    .AddReturn("_name"))

// Block-bodied setter
prop.WithSetter(body => body
    .AddStatement("_name = value;")
    .AddStatement("OnPropertyChanged();"))

Modifiers

prop.WithAccessibility(Accessibility.Public)
prop.AsStatic()
prop.AsVirtual()
prop.AsOverride()
prop.AsAbstract()
prop.AsReadOnly()  // removes setter

Initialization

prop.WithInitializer("new()")
prop.WithInitializer("default")
prop.WithInitializer("\"Default Value\"")
prop.WithBackingField("_name")  // references a backing field

Attributes & XML Documentation

prop.WithAttribute("JsonProperty")
prop.WithXmlDoc("Gets or sets the customer name.")

Usings

prop.AddUsing("System.Text.Json.Serialization")

Properties

prop.Name   // string
prop.Type   // string

FieldBuilder

Create field declarations.

Factory Methods

Method Description
For(string name, string type) Create a field
Parse(string signature) Parse from signature

Modifiers

field.WithAccessibility(Accessibility.Private)
field.AsStatic()
field.AsReadonly()
field.AsConst()

Initialization

field.WithInitializer("string.Empty")
field.WithInitializer("42")

Attributes & XML Documentation

field.WithAttribute("NonSerialized")
field.WithXmlDoc("The backing field for Name.")

Usings

field.AddUsing("System")

Properties

field.Name   // string
field.Type   // string

ConstructorBuilder

Create constructor declarations.

Factory Methods

Method Description
For(string typeName) Create a constructor for the given type

Modifiers

ctor.WithAccessibility(Accessibility.Public)
ctor.AsStatic()
ctor.AsPrimary()  // for primary constructors

Parameters

ctor.AddParameter("name", "string")
ctor.AddParameter("email", "string", p => p.WithDefaultValue("null"))
ctor.AddParameter(parameterBuilder)

Body

ctor.WithBody(body => body
    .AddStatement("Name = name;")
    .AddStatement("Email = email;"))

Constructor Chaining

ctor.CallsBase("name", "email")  // : base(name, email)
ctor.CallsThis("name")           // : this(name)

Attributes & XML Documentation

ctor.WithAttribute("Obsolete")
ctor.WithXmlDoc("Initializes a new instance of the Customer class.")

Properties

ctor.IsPrimary      // bool
ctor.Parameters     // ImmutableArray<ParameterBuilder>
ctor.Usings         // ImmutableArray<string>

ParameterBuilder

Create parameter declarations.

Factory Methods

Method Description
For(string name, string type) Create a parameter

Configuration

param.WithDefaultValue("0")
param.WithDefaultValue("\"default\"")
param.AsRef()
param.AsOut()
param.AsIn()
param.AsParams()
param.AsThis()  // for extension method target

Properties

param.Name              // string
param.Type              // string
param.IsExtensionTarget // bool

TypeParameterBuilder

Create generic type parameter declarations.

Factory Methods

Method Description
For(string name) Create a type parameter

Constraints

tp.WithConstraint("IDisposable")
tp.AsClass()       // where T : class
tp.AsStruct()      // where T : struct
tp.AsNotNull()     // where T : notnull
tp.WithNewConstraint()  // where T : new()

Properties

tp.Name           // string
tp.HasConstraints // bool

BodyBuilder

Build method and property bodies.

Factory Methods

Method Description
Empty() Create an empty body

Adding 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 syntax

Properties

body.IsEmpty  // bool

AttributeBuilder

Create attribute declarations.

Factory Methods

Method Description
For(string name) Create an attribute

Arguments

attr.WithArgument("\"value\"")
attr.WithArguments("1", "2", "3")
attr.WithNamedArgument("Name", "\"value\"")

Usings

attr.AddUsing("System.ComponentModel")

Properties

attr.Name    // string
attr.Usings  // ImmutableArray<string>

XmlDocumentationBuilder

Create XML documentation comments.

Factory Methods

Method Description
Create() Create empty documentation
WithSummary(string) Create with just a summary
From(XmlDocumentation) Create from existing parsed documentation

Content

doc.Summary("Gets the customer name.")
doc.Remarks("This method queries the database.")
doc.Returns("The customer name if found; otherwise, null.")
doc.Value("The property value.")
doc.Param("id", "The customer identifier.")
doc.TypeParam("T", "The entity type.")
doc.Exception("InvalidOperationException", "Thrown when...")
doc.SeeAlso("OtherClass")
doc.Example("<code>var name = GetName(123);</code>")

Properties

doc.HasContent  // bool

EmitOptions

Configure code emission.

Properties

Property Type Description
ValidationLevel ValidationLevel None, Syntax, Semantic, Full
Indentation string Indentation string (default: 4 spaces)
EndOfLine string Line ending (default: \n)
HeaderComment string Comment at top of file
LicenseHeader string? License header text

Static Instances

EmitOptions.Default      // syntax validation, standard formatting
EmitOptions.NoValidation // skip validation

Usage

var options = EmitOptions.Default with
{
    ValidationLevel = ValidationLevel.Semantic,
    HeaderComment = "// Auto-generated"
};

var result = builder.Emit(options);

ValidationLevel Enum

Value Description
None No validation
Syntax Syntax validation only (default)
Semantic Semantic validation (requires compilation)
Full Full validation

OptionalEmit

The result of emitting code.

var result = builder.Emit();

// Check if valid
if (result.IsValid(out var valid))
{
    string code = valid.Code;                    // Formatted C# code
    CompilationUnitSyntax syntax = valid.Syntax; // Roslyn syntax tree
}

// Check if invalid
if (result.IsNotValid(out var diagnostics))
{
    foreach (var diagnostic in diagnostics)
    {
        Console.WriteLine(diagnostic.GetMessage());
    }
}

// Get diagnostics (warnings even if valid)
ImmutableArray<Diagnostic> diags = result.Diagnostics;

Complete Example

var result = TypeBuilder
    .Class("CustomerRepository")
    .InNamespace("MyApp.Data")
    .AddUsing("System")
    .AddUsing("System.Threading.Tasks")
    .Implements("ICustomerRepository")
    .WithXmlDoc("Repository for customer data access.")
    .AddField("_context", "DbContext", f => f
        .WithAccessibility(Accessibility.Private)
        .AsReadonly())
    .AddConstructor(ctor => ctor
        .WithAccessibility(Accessibility.Public)
        .AddParameter("context", "DbContext")
        .WithBody(body => body.AddStatement("_context = context;")))
    .AddMethod("GetByIdAsync", m => m
        .WithReturnType("Task<Customer?>")
        .WithAccessibility(Accessibility.Public)
        .Async()
        .AddParameter("id", "Guid")
        .AddParameter("cancellationToken", "CancellationToken", p => p.WithDefaultValue("default"))
        .WithXmlDoc(doc => doc
            .Summary("Gets a customer by identifier.")
            .Param("id", "The customer identifier.")
            .Param("cancellationToken", "Cancellation token.")
            .Returns("The customer if found; otherwise, null."))
        .WithBody(body => body
            .AddReturn("await _context.Customers.FindAsync(new object[] { id }, cancellationToken)")))
    .Emit();

if (result.IsValid(out var valid))
{
    context.AddSource("CustomerRepository.g.cs", valid.Code);
}

Real-World Usage

Generating from Analyzed Symbols

// Generate a module class from analyzed type information
return TypeBuilder.Parse($"public static partial class {model.EffectsContainerName}")
    .AddUsings(usings)
    .InNamespace(model.Namespace)
    .AddNestedType(module)
    .Emit(options ?? EmitOptions.Default);

Adding Methods Dynamically

var builder = TypeBuilder.Class("Generated");

foreach (var method in methods)
{
    builder = builder.AddMethod(method.Name, m => m
        .AsStatic()
        .WithReturnType($"Eff<RT, {method.ReturnType}>")
        .AddParameter("input", method.InputType)
        .WithXmlDoc(method.XmlDocumentation)
        .WithExpressionBody(GenerateBody(method)));
}

Using Parse for Complex Signatures

// Parse handles complex signatures more naturally
var method = MethodBuilder.Parse("public async Task<IEnumerable<Customer>> GetAllAsync()")
    .AddParameter("filter", "CustomerFilter", p => p.WithDefaultValue("null"))
    .WithBody(body => body
        .AddStatement("var query = _context.Customers.AsQueryable();")
        .AddStatement("if (filter != null) query = query.Where(filter.ToPredicate());")
        .AddReturn("await query.ToListAsync()"));

License

RPL-1.5 (Reciprocal Public License) — Real reciprocity, no loopholes.

You can use this code, modify it, and share it freely. But when you deploy it — internally or externally, as a service or within your company — you share your improvements back under the same license.

Why? We believe if you benefit from this code, the community should benefit from your improvements. That's the deal we think is fair.

Personal research and experimentation? No obligations. Go learn, explore, and build.

See LICENSE for the full legal text.