I reimplemented a source generator originally built by Microsoft. It was made for high-performance logging scenarios—very optimized, but also a bit dense in terms of how the code was structured. So I rebuilt it using my own library, Nest, which I’ve been working on for structured code generation.
The goal wasn’t to change what it does—it still produces the same output—but to explore how it can be done in a more composable and maintainable way. Nest gives me a much better mental model for building generators, especially when you’re juggling indentation, conditionals, and nesting.
Here’s a breakdown of how both implementations approach things differently.
_builder.Append($@"
{nestedIndentation}public override string ToString()
{nestedIndentation}{{
");
GenVariableAssignments(lm, nestedIndentation);
string formatMethodBegin = !lm.Message.Contains('{') ? "" :
_hasStringCreate ? "string.Create(global::System.Globalization.CultureInfo.InvariantCulture, " :
"global::System.Diagnostics.CodeAnalysis.FormattableString.Invariant(";
string formatMethodEnd = formatMethodBegin.Length > 0 ? ")" : "";
_builder.Append($@"
{nestedIndentation}return {formatMethodBegin}$""{ConvertEndOfLineAndQuotationCharactersToEscapeForm(lm.Message)}""{formatMethodEnd};
{nestedIndentation}}}
");Manual indentation tracking (
{nestedIndentation})
Formatting and logic mixed together
Easy to break formatting if you're not constantly watching spacing and newlines
b.L("public override string ToString()").B(b =>
{
VariableAssignmentsForToString(b, lm);
if (ConvertEndOfLineAndQuotationCharactersToEscapeForm(lm.Message) is string escaped_msg)
{
b.L();
if (!lm.Message.Contains('{'))
b.L($"return `{escaped_msg}`;");
else if (m_HasStringCreate)
b.L($"return string.Create(global::System.Globalization.CultureInfo.InvariantCulture, $`{escaped_msg}`);");
else
b.L($"return global::System.Diagnostics.CodeAnalysis.FormattableString.Invariant($`{escaped_msg}`);");
}
});Nest handles indentation and braces automatically
Logic stays clean and scoped
LoggerClass parent = lc.ParentClass;
var parentClasses = new List<string>();
while (parent != null)
{
parentClasses.Add($"partial {parent.Keyword} {parent.Name}");
parent = parent.ParentClass;
}
for (int i = parentClasses.Count - 1; i >= 0; i--)
{
_builder.Append($@"
{nestedIndentation}{parentClasses[i]}
{nestedIndentation}{{");
nestedIndentation += " ";
}
_builder.Append($@"
{nestedIndentation}partial {lc.Keyword} {lc.Name}
{nestedIndentation}{{");Manual depth tracking
Every nesting level updates the indentation string
Not reusable outside of this specific case
var logic = (ITextBuilder b) =>
{
b.L($"partial {keyword} {name}").B(b =>
{
GenerateLogger(b, lc);
});
};
var parent = lc.ParentClass;
while (parent != null)
{
logic = b =>
{
b.L($"partial {keyword} {name}")
.B(logic);
};
parent = parent.ParentClass;
}Build your logic as nested functions
No need to manually manage indentation
Works the same whether you’re 1 level deep or 10
In some cases, you want to drop in a block of static C#—like a helper class—but keep everything flowing through the same system.
if (m_NeedEnumerationHelper)
GenerateEnumerationHelper(b);And GenerateEnumerationHelper just uses raw string literals:
b.L(
$$$"""
/// <summary> ... </summary>
[GeneratedCode(...)]
internal static class __LoggerMessageGenerator
{
public static string Enumerate(global::System.Collections.IEnumerable? enumerable)
{
// ...
}
}
"""
);Just paste raw C# as-is
Still fits into the Nest pipeline without doing anything special
For example, assigning variables inside the generated ToString() is something I extracted out into a reusable function:
private void VariableAssignmentsForToString(ITextBuilder b, LoggerMethod lm)
{
foreach (KeyValuePair<string, string> t in lm.TemplateMap)
{
int index = 0;
foreach (LoggerParameter p in lm.TemplateParameters)
{
ReadOnlySpan<char> template = RemoveSpecialSymbol(t.Key.AsSpan());
ReadOnlySpan<char> parameter = RemoveSpecialSymbol(p.Name.AsSpan());
if (template.Equals(parameter, StringComparison.OrdinalIgnoreCase))
break;
index++;
}
if (index < lm.TemplateParameters.Count)
{
if (lm.TemplateParameters[index].IsEnumerable)
{
b.L($"var {t.Key} = global::__LoggerMessageGenerator.Enumerate((global::System.Collections.IEnumerable ?)this.{NormalizeSpecialSymbol(lm.TemplateParameters[index].CodeName)});");
m_NeedEnumerationHelper = true;
}
else
{
b.L($"var {t.Key} = this.{NormalizeSpecialSymbol(lm.TemplateParameters[index].CodeName)};");
}
}
}
}Self-contained
Easy to test or reuse elsewhere
Doesn’t care where it gets used — just needs a builder and some data
private void GenVariableAssignments(LoggerMethod lm, string nestedIndentation)
{
// Same logic but tied to _builder and manual indentation
}The logic here is embedded directly in the context and tightly coupled to _builder, a manually tracked nestedIndentation, and hardcoded indentation baked right into the strings within the method itself.
That makes testing tricky — if you want to assert the generated output, your test needs to match the exact spacing and formatting. Even a single space off and the test fails. So now, instead of focusing on just the logic, you're also forced to replicate how indentation was being managed elsewhere in the generator.
Nest doesn’t magically do anything you couldn’t already do with StringBuilder. But it lets you structure your output like a real system—made of small, composable pieces, cleanly separated logic, and reusable helpers.
You can mix in raw strings, conditionals, and nested blocks without stressing over indentation or formatting quirks.
And just to be clear: both the original and the Nest-based generator produce the same output. You can check the Output/ folder for examples. I’ve added a bunch of test cases and snapshots to confirm this—aside from a few minor whitespace or line break differences, the output is completely identical.