C# incremental source generator that emits common utility types directly into your assembly. Zero runtime dependencies, zero unused code - only what you call gets generated.
- Only include what you use - methods you never call are never generated
- Zero runtime dependencies - everything is emitted as source into your project, no external library references needed
- Code writer - a
StringBuilder-based code writer with automatic indentation and append methods built for source generation - Performant - heavy use of spans and pools in generated code
dotnet add package Hertzole.SourceGenUtils
<PackageReference Include="Hertzole.SourceGenUtils" Version="*"/>All types are emitted under the Hertzole.SourceGen namespace.
A StringBuilder-based code writer with automatic indentation and a fluent API.
// Must be disposed to return string buffers
using var writer = new CodeWriter();
writer
// Wraps your code in a namespace with automatic indentation
.AppendNamespace("MyApp.Models")
.AppendLine("public class User");
// Wraps your code in a block ({ }) with automatic indentation
using (writer.WithBlock())
{
writer.AppendLine("public string Name { get; set; }");
writer.AppendLine("public string GetName()");
using (writer.WithBlock())
{
writer.AppendLine("if (string.IsNullOrEmpty(Name))");
// Indents 4 spaces
writer.Indent++;
writer.AppendLine("return \"No name\"");
// Indents back
writer.Indent--;
writer.AppendLine("return Name");
}
}
// Use ToString() to get the final result
Console.WriteLine(writer.ToString());Supports all the built-in types, appending the current line, and creating new lines. Also has helper methods for
appending common operations, like namespaces, [GeneratedCode] attribute, preprocessor symbols, and more.
Stack-based object pool with factory and lifecycle callbacks (onGet, onReturn, onDispose).
// Pool should be disposed when no longer used
using var pool = new ObjectPool<StringBuilder>(
create: () => new StringBuilder(),
onGet: sb => sb.Clear(),
onReturn: sb => { }
);
// Gets from the pool
var sb = pool.Get();
// Return to pool
pool.Return(sb);
// Use a scope to automatically return items
using (var scope = pool.Get(out var sb))
{
sb.Append("hello");
}Static pools for common collections. All follow the same Get() / Get(out T) / Return() pattern with automatic
clearing on return.
| Type | Wraps |
|---|---|
StringBuilderPool |
StringBuilder (initial capacity 1024) |
ListPool<T> |
List<T> |
HashSetPool<T> |
HashSet<T> |
StackPool<T> |
Stack<T> |
QueuePool<T> |
Queue<T> |
using var list = ListPool<int>.Get(out var items);
items.Add(42);
// returned and cleared on disposereadonly struct providing value-based equality for arrays.
var a = new EquatableArray<string>(new[] { "hello", "world" });
var b = new EquatableArray<string>(new[] { "hello", "world" });
Console.WriteLine(a == b); // TruePool-backed array builder using ArrayPool<T>.Shared. Supports Add, AddRange, Remove, RemoveAt, and Clear.
More performant than List<T> when you just need to quickly construct arrays.
Bonus! Calling ToString() on ArrayBuilder<char> returns the built string, like a StringBuilder.
using var builder = new ArrayBuilder<int>();
builder.Add(1);
builder.Add(2);
builder.AddRange(stackalloc[] { 3, 4 });
// buffer returned to pool on disposeVariable name transformation utilities for code generation. Nicify, remove prefixes (m_, _, k), uppercase start,
and detect event handler patterns. All, of course, support spans along with simply returning strings.
VariableNames.NicifyVariableName("m_playerName"); // "PlayerName"
VariableNames.RemovePrefix("kConstant"); // "Constant"
VariableNames.UppercaseStart("playerPoints"); // "PlayerPoints"Roslyn syntax helpers to resolve attribute symbols and find field declarations from nested syntax nodes.
Debug-only file-based logging (#if DEBUG). Writes timestamped entries to {AssemblyName}.log.
You don't need to put them in a #if DEBUG preprocessor symbol, they simply just get removed in release builds!
Log.Info("Generation started");
Log.Warning("Unsupported syntax node");
Log.Error("Failed to resolve symbol");The generator operates in two phases:
- Shell generation - emits
partialtype stubs so your code can reference the utilities during compilation. - Incremental implementation - scans for invocations and object creation expressions matching known types. Only called methods get full implementations. Uncalled methods compile as empty stubs.
This means adding the NuGet package has a minimal impact on your assembly size until you actually use a utility. When nothing is used, it adds roughly 11 kb to your assembly.
- .NET Standard 2.0+ (source generator target)
- Roslyn 4.x+