⚡ Logging Without String Allocation
Interpolation allocates strings even if log level is off. Custom interpolated string handlers defer formatting until needed.
📝 Custom Handler
[InterpolatedStringHandler]
public ref struct DebugLogHandler
{
private readonly bool _enabled;
private StringBuilder? _builder;
public DebugLogHandler(int literalLength, int formattedCount,
bool enabled, out bool shouldAppend)
{
_enabled = enabled;
shouldAppend = enabled;
if (enabled)
_builder = new StringBuilder(literalLength + formattedCount * 8);
}
public void AppendLiteral(string s)
{
if (_enabled) _builder!.Append(s);
}
public void AppendFormatted(T value)
{
if (_enabled) _builder!.Append(value);
}
internal string GetFormattedString() => _builder?.ToString() ?? ";
}
public static class Debug
{
public static void WriteLine(DebugLogHandler handler)
{
if (Debugger.IsAttached)
Console.WriteLine(handler.GetFormattedString());
}
}
🎯 Usage and Performance
// Zero allocation if debugger not attached!
Debug.WriteLine($"User {userId} logged in at {DateTime.Now}");
// Same for logging libraries
public void LogDebug(LogLevel level, DebugLogHandler handler)
{
if (_logLevel <= level)
Console.WriteLine(handler.GetFormattedString());
}
_logger.LogDebug($"Processing order {orderId} with total {total:C}");
💡 When to Use
- High-frequency logging (1000+ logs/second)
- Disable logging in production → zero allocation
- Custom logging frameworks
- String building with conditional formatting
"Debug logs in production were off but still allocated strings. Custom interpolated handler: zero allocation when disabled. GC pressure dropped 60%. Performance logging is now free."
