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¶
Emit¶
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¶
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¶
Usings¶
Properties¶
FieldBuilder¶
Create field declarations.
Factory Methods¶
| Method | Description |
|---|---|
For(string name, string type) | Create a field |
Parse(string signature) | Parse from signature |
Modifiers¶
Initialization¶
Attributes & XML Documentation¶
Usings¶
Properties¶
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¶
Constructor Chaining¶
Attributes & XML Documentation¶
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¶
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¶
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¶
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¶
Properties¶
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¶
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.