Frames
Frames are the fundamental building blocks of the JasperFx code generation model. Each Frame is responsible for writing one or more lines of C# source code into a generated method body.
The Frame Base Class
All frames derive from the abstract Frame class in JasperFx.CodeGeneration.Frames. A frame declares:
- Whether it is async (its constructor receives a
bool isAsyncflag). - Which Variables it creates (the
createslist). - Which Variables it uses/depends on (the
useslist). - An optional Next frame in the chain.
The single abstract method every frame must implement:
void GenerateCode(GeneratedMethod method, ISourceWriter writer)Inside GenerateCode, the frame writes C# text through the ISourceWriter and then typically calls Next?.GenerateCode(method, writer) to continue the chain.
SyncFrame and AsyncFrame
JasperFx provides two convenience base classes so you do not have to pass the isAsync flag manually:
SyncFrame-- setsisAsynctofalse. Use for frames that produce synchronous code.AsyncFrame-- setsisAsynctotrue. Use when the generated code mustawaitsomething.
Writing a Custom Sync Frame
public class LogMessageFrame : SyncFrame
{
private readonly string _message;
private Variable? _logger;
public LogMessageFrame(string message)
{
_message = message;
}
public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
writer.Write($"Console.WriteLine(\"{_message}\");");
// Always call through to the next frame in the chain
Next?.GenerateCode(method, writer);
}
}Key points:
- Inherit from
SyncFrame(orAsyncFramefor async code). - Override
GenerateCodeto write your C# lines through theISourceWriter. - Always call
Next?.GenerateCode(method, writer)at the end so the next frame in the chain can emit its code.
Writing a Custom Async Frame
public class LoadEntityFrame : AsyncFrame
{
private readonly Type _entityType;
private Variable? _id;
public LoadEntityFrame(Type entityType)
{
_entityType = entityType;
}
public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
var entityVariable = Create(_entityType);
writer.Write(
$"var {entityVariable.Usage} = await repository.LoadAsync<{_entityType.Name}>({_id?.Usage ?? "id"});");
Next?.GenerateCode(method, writer);
}
}Async frames work identically except they extend AsyncFrame and typically emit await expressions.
Wrapping Frames
A frame can wrap subsequent frames by setting Wraps = true. This is useful for try/catch blocks, using blocks, timing wrappers, and similar patterns.
public class StopwatchFrame : SyncFrame
{
public StopwatchFrame()
{
// Mark this frame as wrapping inner frames
Wraps = true;
}
public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
writer.Write("var stopwatch = System.Diagnostics.Stopwatch.StartNew();");
writer.Write("BLOCK:try");
// Let the inner frames generate their code
Next?.GenerateCode(method, writer);
writer.FinishBlock(); // end try
writer.Write("BLOCK:finally");
writer.Write("stopwatch.Stop();");
writer.Write("Console.WriteLine($\"Elapsed: {stopwatch.ElapsedMilliseconds}ms\");");
writer.FinishBlock(); // end finally
}
}When Wraps is true, the code generation engine knows that this frame opens a scope that encloses the next frame(s).
Creating Variables from a Frame
Frames can declare that they "create" a variable. This is how the variable resolution system knows which frame must run before another:
// Inside a Frame subclass
var result = Create<MyService>(); // registers the variable in this.createsAny downstream frame that needs a MyService variable will automatically depend on this frame.
Variable Dependencies
Frames can also declare variables they "use":
uses.Add(someVariable);The code generation engine uses these creates and uses declarations to determine the correct ordering of frames and to resolve variables across the method.
Frame Ordering
When you add frames to a GeneratedMethod, the engine:
- Collects all frames and their variable dependencies.
- Topologically sorts frames so that a frame producing a variable always appears before frames consuming it.
- Chains frames together via their
Nextproperty. - Calls
GenerateCodeon the first frame, which cascades through the chain.
You do not need to worry about insertion order for most cases -- the dependency graph handles it. However, if two frames have no dependency relationship, they will appear in the order you added them.
